Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux normalizr + dealing with reduced responses

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?

like image 249
AndrewMcLagan Avatar asked Jul 01 '16 05:07

AndrewMcLagan


People also ask

How do I use normalizr with Redux?

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:

How do Redux Selectors find the post data?

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.

What does normalized state mean?

"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

What is normalizr and how does it work?

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.


1 Answers

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;
  }
}
like image 78
1ven Avatar answered Sep 23 '22 19:09

1ven