Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Normalize API data for use in Redux

I have the following data coming from an API:

const data = {
  id: 1,
  name: 'myboard',
  columns: [
    {
      id: 1,
      name: 'col1',
      cards: [
        { id: 1, name: 'card1' },
        { id: 2, name: 'card2' }
      ]
    },
    {
      id: 2,
      name: 'col2',
      cards: [
        { id: 3, name: 'card3' },
        { id: 4, name: 'card4' }
      ]
    },
  ]
}

As you can see, there are essentially 3 nested levels. The top level contains an id, name and a list of columns. Each column contains an id, name and a list of cards. Each card has an id and name.

I wish to normalize the data for use in Redux as presented here. I am using normalizr to do this as follows:

const card = new schema.Entity('cards');
const column = new schema.Entity('columns', {
  cards: [card]
});
const board = new schema.Entity('boards', {
  columns: [column]
});

normalize(data, board)

This results in the following:

{
  "entities": {
    "cards": {
      "1": {
        "id": 1,
        "name": "card1"
      },
      "2": {
        "id": 2,
        "name": "card2"
      },
      "3": {
        "id": 3,
        "name": "card3"
      },
      "4": {
        "id": 4,
        "name": "card4"
      }
    },
    "columns": {
      "1": {
        "id": 1,
        "name": "col1",
        "cards": [1, 2]
      },
      "2": {
        "id": 2,
        "name": "col2",
        "cards": [3, 4]
      }
    },
    "boards": {
      "1": {
        "id": 1,
        "name": "myboard",
        "columns": [1, 2]
      }
    }
  },
  "result": 1
}

What I can't seem to figure out is how to have each section (ie: cards, columns, boards) split into two sections, namely byId and allIds as per the Redux article referenced above.

Essentially this is to make ordering, sorting etc easier in a React application. Im using the latest version of normalizr (3.2.4).

like image 277
darkpool Avatar asked Apr 12 '26 07:04

darkpool


1 Answers

Here is a CodeSandbox with an example of how you can set up the reducers to handle the normalized state.

Essentially, you will end up with something like this for each of your entities:

// lambda or function - whatever your preference is
const cardsById = (state = {}, action) => {
  // if, case, handler function - whatever your preference is
  if (action.type === 'ADD_DATA') { // or whatever your initial data load type is
    return { ...state, ...action.payload.cards }
  }
  return state
}

const allCards = (state = [], action) => {
  if (action.type === 'ADD_DATA') { // or whatever your initial data load type is
    return [...state, ...Object.keys(action.payload.cards)]
  }
  return state
}

const cards = combineReducers({
  byId: cardsById,
  allIds: allCards
})

and then combine all of those together:

export default combineReducers({
  cards,
  columns,
  boards
})

The action creators for this are as follows:

const addData = ({ entities }) => ({
  type: 'ADD_DATA',
  payload: entities // note the rename - this is not required, just my preference
})

// I used a thunk, but theory is the the same for your async middleware of choice
export const getData = () => dispatch => dispatch(addData(normalize(data, board)))

Hope this helps. Remember that you will need to maintain both the byId and allIds for each entity as entities are added or removed.

like image 86
Michael Peyper Avatar answered Apr 14 '26 20:04

Michael Peyper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!