简体   繁体   中英

React Redux - Reducer Mutation

I have a reducer in which is mutating the state of the current state object by creating a shallow copy. The reducer works by updating a value in a stored object template then returning the full state object. The reducer does update the store and i am able to see the change on refresh with the persisted state being regenerated from localStorage. However, components subscribed using connect() do not get a re-render. How do i write this reducer the correct way to not mutate the state?

the reducer:

case UPDATE_STYLE:
  // Update the style value of the template
  let newState = {
    ...state
  }
  newState
      .templates[action.payload.selectedTemplateType]
      .content[action.payload.selectedTemplate]
      .template
      .styles[action.payload.styleKey][action.payload.fieldKey]
      .value = action.payload.value;

  // mutated state - not firing rerenders
  return newState;

firing the dispatch call (works):

onStyleChange(styleKey, fieldKey, value){
    this.props.dispatch(
      updateStyle(selectedTemplateType, selectedTemplate, styleKey, fieldKey, value)
    );
  }

how the child component is connected (not rendering):

const mapStateToProps = (state) => ({
  templates: state.templates.templates,
  selectedTemplateType: state.templateTypeSelection.selectedTemplateType,
  selectedTemplate: state.templateSelection.selectedTemplate
})

export default connect(mapStateToProps)(Index)

In your reducer, after the action is handled, the object state.templateTypeSelection.selectedTemplateType is the same object, even though some property deep inside the object changed. The other props have the same issue. So, to the component, the props are the same props, therefore the component is not rerendered.

There are a few solutions to this. 1 To flatten your reducer state. (doesn't seem to work for you) 2 To deep copy the reducer state. 3 When you do `mapStateToProps, try to map the innermost property, instead of the top level property.

For solution 2, what you can do is to create classes for the top level property of the reducer state. For example, you can create a class called TemplateTypeSelection for state.templateTyepeSelection . When you handle the actions in the reducer, for the new state, you use new TemplateTypeSelection(prevState.templateTypeSelection) to create a new object for state.templateTypeSelection(You may want to use a constructor to do a shallow copy of the state.templateTypeSelection ). Since, the state.templateTypeSelection is a new object now, your component should be able to rerender now. The issue with this method is that, since every time you handle an action, you would create a new object, there might be some unnecessary rerender. (This is just an example of how to do it. Prob not a good example, because in your component, you're connecting state.templateTypeSelection.selectedTemplateType , not state.templateTypeSelection . Hope you get the idea. )

Based on my experience, a combination of method 2 and 3 works best. Also, you may want to consider split the reducer into several reducers.

Another post already mentioned deep-copy as a quick solution (although you might lose performance). However, even though it's a little tricky, the spread syntax ( ... ) might come to your rescue:

let newState = { ...state, templates: {
  { ...state.templates, [action.payload.selectedTemplateType]: {
    { ...state.templates[action.payload.selectedTemplateType], content: {
      { ...state.templates[action.payload.selectedTemplateType].content, [ // and so on

At least, you'd only create new object for what has actually changed.

On that note: you might want to take a look at immutableJS which provides lots of helpers with this and is less prone to errors. For instance, there is mergeDeep to help.

When you don't want to do such a profound transition, there's also deep merge modules on npm (just google for "merge deep npm"); I just didn't actually try any of these...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM