30
votes

When writing a react-redux application, I need to keep both application and UI state in the global state tree. What is the best approach to design it's shape?

Lets say I have a list of todo items:

{
  items: [
    { id: 1, text: 'Chew bubblegum' },
    { id: 2, text: 'Kick ass' }
  ]
}

Now I want to let the users select and expand the items. There are (at least) two options how to model the state shape:

{
  items: [
    { id: 1, text: 'Chew bubblegum', selected: true, expanded: false },
    { id: 2, text: 'Kick ass', selected: false, expanded: false }
  ]
}

But this is mixing the UI state (selected and expanded) with the application state. When I save the todo list to the server, I want to save just the application state, not the UI state (in real-world app, UI state can contain state of modal dialogs, error messages, validation status etc).

Another approach is to keep another array for the UI state of the items:

{
  items: [
    { id: 1, text: 'Chew bubblegum' },
    { id: 2, text: 'Kick ass' }
  ],
  itemsState: [
    { selected: true, expanded: false },
    { selected: false, expanded: false }
  ]
}

Then you have to combine those two states when rendering an item. I can imagine that you can zip those two arrays in the connect function to make rendering easy:

const TodoItem = ([item, itemState]) => ...;
const TodoList = items => items.map(item => (<TodoItem item={item} />));

export default connect(state => _.zip(state.items, state.itemsState))(TodoList);

But updates to state can be painful, because items and itemsState must be kept in sync:

  1. When removing an item, corresponding itemState must be removed.
  2. When reordering items, itemsState must be reordered too.
  3. When the list of todo items is updated from the server, it is necessary to keep ids in the UI state and do some reconciliation.

Is there any other option? Or is there any library that helps keeping the app state and UI state in sync?

2
Did you find any solution/implementation for this? - yadav_vi

2 Answers

14
votes

Another approach inspired by normalizr:

{
    ids: [12,11], // registry and ordering
    data: {
        11: {text: 'Chew bubblegum'},
        12: {text: 'Kick ass'}
    },
    ui: {
        11: { selected: true, expanded: false },
        12: { selected: false, expanded: false }
    }
}
0
votes

I'm currently looking at this myself for a side project. I'm going to approach it similar to Rick's method above. The data{} serves as the source of truth and you use that to push local ui changes into (reflecting the most current state). You do need to merge the data and ui together before render, and I myself have tried that in a few places. I will say, as far as keeping in sync, it shouldn't be too bad. Basically, whenever you save/fetch data, you're updating data{} and clearing out ui{} to prepare for the next use case.