Blog>
Snippets

State Normalization for Recursive API Data

Show how to normalize and store the data in the Redux state when dealing with recursive API responses to ensure efficient data retrieval and updates.
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';

// Define an entity adapter with the structure { ids: [], entities: {} }
const myEntityAdapter = createEntityAdapter();

// Create a slice with an empty initial state
const mySlice = createSlice({
  name: 'myEntities',
  initialState: myEntityAdapter.getInitialState(),
  reducers: {
    // Reducer to add entities recursively
    addEntities: (state, action) => {
      myEntityAdapter.addMany(state, action.payload);
    }
  }
});

// Export the action creator
export const { addEntities } = mySlice.actions;

// Export the reducer
export default mySlice.reducer;
This piece of code initializes a Redux slice using createEntityAdapter. It sets up a standard structure for storing normalized data, provides a reducer to handle adding entities recursively, and exports both the action creator and the reducer.
import { addEntities } from './mySlice';

// Function to normalize nested data recursively
function normalizeData(items) {
  // Create a Map to store normalized items
  const normalizedItems = new Map();

  // Define a local function that walks through the nested items
  function processItem(item) {
    // Add item to the Map, avoiding duplication
    normalizedItems.set(item.id, { ...item, children: item.children.map(child => child.id) });

    // Recursively process children
    item.children.forEach(processItem);
  }

  // Start processing the root items
  items.forEach(processItem);

  // Convert Map to an object with ids and entities suitable for the reducer
  return {
    ids: Array.from(normalizedItems.keys()),
    entities: Object.fromEntries(normalizedItems)
  };
}
This function is responsible for normalizing a list of nested items recursively. It processes the data from a nested structure to a flat structure with references (e.g., foreign keys) to children by their IDs instead of embedding the actual objects, and returns an object with 'ids' and 'entities' suitable for our normalized state.
// Example usage within a thunk or a component for dispatching the normalized data
import { useDispatch } from 'react-redux';
import { addEntities } from './mySlice';
import { normalizeData } from './normalizeData';

// Recursive API data as an example
const recursiveApiData = [{
  id: 'item1',
  children: [
    { id: 'item2', children: [] },
    { id: 'item3', children: [
      { id: 'item4', children: [] }
      ]
    }
  ]
}];

const dispatch = useDispatch();

// Normalize and store the API data
const normalized = normalizeData(recursiveApiData);
dispatch(addEntities(normalized));
This part shows an example of how you might handle normalizing and storing data fetched from a recursive API. The recursive API data is normalized using the 'normalizeData' function and then dispatched to the store using the 'addEntities' reducer.