png
png

Reputation: 1130

Where to generate unique IDs for entities in a redux application?

I want to allow users to add the same server Item to the page multiple times (id: 5). To keep track of these Items in our application, we need to generate a unique ID for each instance.

Our item has relational data, so for performance reasons we might normalize the API response in a redux store.

type Item = { id: string; }
type Sku = { id: string; }

type AppStore = {
  items: { [id: string]: Items },
  skus: { [id: string]: Skus },
  itemSkus: { [id: string]: string[] },
}

Working with this API

export interface IAPIResponse<T> {
  data?: T;
  errors?: string[];
}

type IAPIItem = {
  id: string;
  skus: Array<{
    id: string;
  }>;
}

Requesting the data in a typical redux-thunk action creator:

export const addItem = () => async (dispatch) => {
  dispatch(startedAddItem());
  try {
    const response = await get <IAPIResponse<IAPIItem>>('/get/item/5');
    dispatch(succeededAddItem(response));
  } catch (error) {
    console.error('Error', error);
    dispatch(failedAddItem(error));
  }
};

And populate our reducers with their relevant data:

// items reducer
case ItemAction.addSucceeded:
  const apiItem = getApiResource(action.payload);
  const apiErrors = getApiErrors(action.payload);

  if (apiErrors) // Handle errors in state

  if (apiItem) {
    const item = buildItem(apiItem);
    return {
      ...state,
      [item.id]: item,
    }
  }
  break;

// skus reducer
case ItemAction.addSucceeded:
  const apiItem = getApiResource(action.payload);
  const apiErrors = getApiErrors(action.payload);

  if (apiErrors) // Handle errors in state

  if (apiItem) {
    const skus = buildSkus(apiItem.skus);
    const indexedSkus = { ...skus.map(s => ({ [s.id]: s })) };
    return {
      ...state,
      ...indexedSkus,
    }
  }
  break;

// itemSkus reducer
case ItemAction.addSucceeded:
  const apiItem = getApiResource(action.payload);
  const apiErrors = getApiErrors(action.payload);

  if (apiErrors) // Handle errors in state

  if (apiItem) {
    const item = buildLineItem(apiItem);
    const skus = buildSkus(apiItem.skus);

    return {
      [item.id]: skus.map(s => s.id),
    }
  }
  break;

In this pattern we couldn't reliable generate the same unique ids for Item and Skus because the response is being parsed in multiple reducers. Redux suggests we must generate the unique ID before it hits the reducers.

Question: How can I adapt this pattern to parse the response before the reducer, while maintaining the flexibility to read nested api data and parse response body errors in the reducers?

Upvotes: 2

Views: 4432

Answers (1)

markerikson
markerikson

Reputation: 67567

After further discussion on Discord, the OP provided this key bit of information:

There's really only one data flow piece here

  • The React application can request a tire from database when user clicks "add tire" (/tire/add/5)
  • They can request the same tire multiple times
  • They can then modify each instance of that tire to have unique properties
  • Click "save" will send the car and its tires to /car/save

My answer:

The /tire/add API endpoint could just generate a UUID every time it sends a response. Or, on the client side:

const addTire = (sku, otherData) => {
  const response = await post(`/tire/add/${sku}`, otherData);
  response.data.id = uuid();
  dispatch(tireAdded(response.data));
}

or alternately, especially if you're using RTK:

const tiresSlice = createSlice({
  name: "tires",
  initialState,
  reducers: {
    tireAdded: {
      reducer(state, action) {
        // whatever here
      },
      prepare(tireData) {
        return {
          payload: {...tireData, id: uuid()}
        }
      }
    }
  }
})

Overall:

  • don't do anything random in reducers
  • that means generating any random stuff before you dispatch
  • you can preprocess and modify and mangle API responses as much as you want before dispatching an action containing that data

Upvotes: 1

Related Questions