5

For a given reducer, my redux state tree typically looks something like this:

{
  someField: 'some value',
  // ... more fields
  metadata: {
    pending: false,
    fetched: true,
  }
}

Typically, when I do an async request, I fire a SOME_ACTION_REQUEST action, which sets the metadata.pending property to true. It's reset to false again when the matching SOME_ACTION_RESPONSE or SOME_ACTION_ERROR event trickles in later.

However, the way I update the state is a bit verbose:

case actions.SOME_ACTION_REQUEST: {
  return {
    ...state,
    metadata: { ...state.metadata, pending: true },
  };
}

Is there a simpler way of doing this?

Object.assign({}, state, { metadata: { pending: true } }) isn't very readable either.

Kris Selbekk
  • 7,438
  • 7
  • 46
  • 73
  • 2
    Possible duplicate of [Cleaner/shorter way to update nested state in Redux?](http://stackoverflow.com/questions/35592078/cleaner-shorter-way-to-update-nested-state-in-redux) – Harkirat Saluja Sep 30 '16 at 14:34

3 Answers3

2

That's a pretty typical example of doing immutable data updates. You may want to read through the new Structuring Reducers section of the Redux docs for some further information, particularly the Prerequisite Concepts and Immutable Update Patterns pages.

markerikson
  • 63,178
  • 10
  • 141
  • 157
1

You can go for a nested structure of reducer.

where have a function/reducer naming metadata, call this to change the fields.

Example code

const metadata = (state = [], action = {}) => {
    switch (action.type) {
        case actions.SOME_ACTION_REQUEST::
            return {...state, ...action.payload.metadata};

        default:
            return state;
    }
};
const someReducer = (state = initalState, action = {}) => {
    let metadata = {};
    switch (action.type) {
        case actions.SOME_ACTION_REQUEST: {
            metadata = metadata(state.metadata, action)
            return {
                ...state,
                ...metadata
                };
            }
    }
};
mindaJalaj
  • 448
  • 3
  • 11
0

Yeah, it is actually a downside of Redux, and not only -- if you would use MobX, you'd have the same issue. So, the answer is just to generalize it somehow -- write a reducers factory, which will get constants as arguments, and merge them just once, and reuse it for all async actions.

I've created a library for exactly this issue, so feel free to look at it. Also, just keep in mind that raw Redux is pretty verbose anyway, and try to grasp your domain model in the best way. Your example will look like that:

import { createTile } from 'redux-tiles';

const someTile = createTile({
  type: ['some', 'example'],
  fn: ({ api, params }) => api.get('/some/request', params),
});

dispatch(actions.some.example(params));
selectors.some.example(state)); // { isPending: true, error: null, data: null }

Data from the last selector will be updated automatically after response will be gotten -- so no need to write such stuff manually.

Another problem is nested updates, and you'd have to do the same thing, but with bigger amount of verbosity, which I always tried to solve. But, of course, it is a pretty simple library, which tries only to cover simple use-cases -- for complex ones, I definitely recommend you to try something custom-tailored.

Bloomca
  • 1,784
  • 13
  • 17