Normalizr is great at creating structured JSON repositories of entities.
We have many cases displaying lists of data e.g. posts
that have been normalised. Where posts
are listed the API response is limited to a few key fields.
We also have cases where we display one of these posts
although we now need to fetch the FULL JSON entity from the API with all the fields.
How is it best to deal with this?
A a seperate reducer, thunk/saga, selectors and actions?
B simply insert the extended version of thepost
fetched from the API into the reducer. Reusing the selectors etc from before?
To get normalizr to work we had to create the schemas for the different entities we wanted to store in our Redux store. We then need to set up a simple action in order to deserialize our order correctly into the redux store: Once you’ve got the schema and the actions, the reducer is surprisingly simple:
Since the selectors are called with the root Redux state object, they need to know where to find our posts data in the Redux state, so we pass in a small selector that returns state.posts.
"Normalized state" means that: We only have one copy of each particular piece of data in our state, so there's no duplication Data that has been normalized is kept in a lookup table, where the item IDs are the keys, and the items themselves are the values. There may also be an array of all of the IDs for a particular item type
After coming across this great article we realised that normalizr.js would be a great fit for what we wanted. Essentially, normalizr takes a deeply nested javascript object (like the Order above) and flattens it out. Checkout the Github repo for more details about exactly how it works.
Think of the app's state as a database. I suggest you to use this state shape:
{
entities: {
// List of normalized posts without any nesting. No matter whether they have all fields or not.
posts: {
'1': {
id: '1',
title: 'Post 1',
},
'2': {
id: '2',
title: 'Post 2',
}
},
},
// Ids of posts, which need to displayed.
posts: ['1', '2'],
// Id of full post.
post: '2',
}
First of all, we are creating our normalizr
schemas:
// schemas.js
import { Schema, arrayOf } from 'normalizr';
const POST = new Schema('post');
const POST_ARRAY = arrayOf(POST);
After success response, we are normalizing response data and dispatching the action:
// actions.js/sagas.js
function handlePostsResponse(body) {
dispatch({
type: 'FETCH_POSTS',
payload: normalize(body.result, POST_ARRAY),
});
}
function handleFullPostResponse(body) {
dispatch({
type: 'FETCH_FULL_POST',
payload: normalize(body.result, POST),
});
}
In reducers, we need to create entities
reducer, which will be listening all actions and if it has entities
key in payload, would add this entities to the app state:
// reducers.js
import merge from 'lodash/merge';
function entities(state = {}, action) {
const payload = action.payload;
if (payload && payload.entities) {
return merge({}, state, payload.entities);
}
return state;
}
Also we need to create corresponding reducers to handle FETCH_BOARDS
and FETCH_FULL_BOARD
actions:
// Posts reducer will be storing only posts ids.
function posts(state = [], action) {
switch (action.type) {
case 'FETCH_POSTS':
// Post id is stored in `result` variable of normalizr output.
return [...state, action.payload.result];
default:
return state;
}
}
// Post reducer will be storing current post id.
// Further, you can replace `state` variable by object and store `isFetching` and other variables.
function post(state = null, action) {
switch (action.type) {
case 'FETCH_FULL_POST':
return action.payload.id;
default:
return state;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With