简体   繁体   中英

Redux: Reuse a reducer to update multiple state properties

Perhaps it's just a missing piece of information in my Redux knowledge, but even after seaching for hours, I still have no idea how to create a reusable reducer to update many state properties.

Let's say, I create a simple component MySwitch providing an UI to edit boolean values. There is a related action and a reducer to update the state. I understand how to make it work for a specific property in the state. But how can I create the component (and related reducer) to work with any boolean value from the store, without creating a special reducer for each of them?

Let's say, the state looks like this:

{
  items: {
    house: { isBig:true, location: ... },
    car: { isMine:true, isBroken:false, ... }
  },
  books: [
    { id:1, available:true, title:... },
    { id:2, available:false, title:... }, ...
  ]
}

What is the correct mechanism to create various instances of MySwitch to view and edit any given value – like items.house.isBig , items.car.isMine or books[1].available – and update them with the same reducer?

I could inject the property to the component using connect, something like:

ASwitch = connect(state => ({
  valueToEdit: state.items.car.isMine
})(MySwitch)

but I have no clue how to pass it to the reducer and how to let it update the respective part of the state. I thought I could provide the path (ie "items.car.isMine" ) to the action:

export const editBoolean = ( path, value ) => {
  return {
    type: 'BOOL_EDIT',
    payload: { path, value }
  }
}

and then use something like this in the reducer:

case 'BOOL_EDIT': {
  const { path, value } = action.payload;
  return {
    ...state,
    [path]: value
  }
}

but it doesn't work, seems ES6 doesn't support variable computed properties, a property like state["items.car.isMine"] is created instead.

I have no idea how to go on here. Thanks for any help.

I think you have the right idea but your question isn't exactly specific to redux, you just need to clone your state with the updated node you wish to target via your path.

You could use something like this

const singlePathReducer = (state, { payload: { path, value } }) => path.reduce(({ nextState, branch = nextState }), node) => {
  if (node !== path[path.length - 1]) {
    // this was the part you were missing; you need to walk down your state tree
    // by passing a reference of the current branch to the next reduce callback
    return { nextState, branch: nextState[node] };
  }

  // mutate your nextState, this is ok
  branch[node] = value;
  return nextState;

  // don't mutate state, create a copy
}, { nextState: { ...state } });

If I'm not mistaken about how pass by reference works in js this should work. The full implementation would be like this (keeping your singlePathReducer fn separate makes it easier to unit test):

const rootReducer = (state, action) => {
  switch action.type {
    case 'BOOL_EDIT':
      return singlePathReducer(state, action);
    default:
      return state;
  }
};

*Disclaimer: this is somewhat of a Redux anti-pattern to me as it allows setting of any value in the store to any value, using a single action. Use it wisely.

````
const set = require("lodash/set");
const { produce } = require("immer");

case 'BOOL_EDIT': {
  const { path, value } = action.payload;
  return produce(state, draft => {
    set(draft, path, value);
  });
}
```

editBoolean("items.car.isMine", false)

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