Collections ----------- Collections don't have to be any different than models (in other words an array can be returned from `model.getValue()`). See the main [README](../README.md) page to do this without advanced functionality. If you [normalize](https://github.com/paularmstrong/normalizr) your collection you can track action/fetch XHR state for each item in the collection individually. ## Tracking XHR status for individual collection items This contrived example shows a list of products and a product-level loading indicator when a product is being added to cart. Assume our product search response returns a shape of `{ totalCount: _number_, products: [...] }` ### Normalizr Schema This is how the magic happens. We normalize the results and extract the array of product values to an array of ids with the product data stored separately ``` import { schema } from 'normalizr'; // the schema.Entity parameter will match our `entityType` value export productSchema = new schema.Entity('product'); export productCollectionSchema = new schema.Array(productSchema); ``` ### Action Creators This is similar to the basic example except we * apply the `productCollectionSchema` to the response * use a formatter to move the `totalCount` value to `data` (model.data()) because now the `model.value()` must return an array ```javascript import { createActionCreator } from 'restful-redux'; import { productCollectionSchema } from './schemas'; // high level restful-redux action creator for our product collections (search results) const productCollectionActionCreator = createActionCreator({ entityType: 'products', actionPrefix: 'PRODUCTS' }); // high level restful-redux action creator for an individual product const productActionCreator = createActionCreator({ entityType: 'products', actionPrefix: 'PRODUCTS' }); // action creator to initiate a product search fetch export function searchProducts (searchTerm) { return productCollectionActionCreator.fetch({ id: searchTerm, url: `/api/products/search/${searchTerm}`, // this will separate the list of products into an array with a separate entry for each product schema: productCollectionSchema, formatter: function (response) { // to normalize a collection we need the model `value` to be an array so we'll save // the additional attributes as `data` return { result: response.products, data: { // so we can still get the total count with props.model.data().totalCount totalCount: response.totalCount } }; } }); } // action creator to initiate a product related add to cart export function addToCart (productId) { return productCollectionActionCreator.createPostAction({ id: productId, // actionId allows us to track the XHR state of this specific action for this specific product actionId: 'addToCart', url: `/api/product/${productId}`, // by including `fetchConfigMiddleware` (see ./action-creator.md) the body will be serialized // and appropriate JSON headers will be set for you params: { body: { quantity: 1 } }, // we'll assume the response will return the new product details (with a count of the quantity in cart) replaceModel: true }); } ``` ### Reducer ```javascript import { createReducer, chainReducers } from 'restful-redux'; // nothing functionally different here from the basic example export default chainReducers( createReducer({ entityType: 'products', actionPrefix: 'PRODUCTS' }), createReducer({ entityType: 'product', actionPrefix: 'PRODUCT' }) ); ``` ### React Component Model Provider ```javascript import { productCollectionSchema, productSchema } from './schemas'; import { denormalize } from 'normalizr'; import { modelProvider } from 'restful-redux'; import { searchProducts, addToCart } from './actions'; // `model` is automatically injected into this component props by the modelProvider function ProductListComponent ({ model, addToCart }) { const products = model.value(); const numProducts = products && model.data().totalCount; if (model.fetchError()) { // model.fetchError() returns the error payload: { headers, status, statusText, url, value } return