最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to compose redux reducers with dependent state - Stack Overflow

programmeradmin2浏览0评论

I am working on a React/Redux application that allows for "widgets" to be added to a page and manipulated in 2D space. It is a requirement that multiple widgets can be selected and manipulated at once. A simplified version of my current state tree looks something like the following...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    widgets: [ "widget_1", "widget_3" ]
  }
}

I currently have this tree managed by 2 reducers one managing widgets state and the other managing selection state. The selection state reducer can be simplified as (note: I am using Immutable.js too)...

currentlySelectedWidgets(state = EMPTY_SELECTION, action) {
  switch (action.type) {
    case SET_SELECTION:
      return state.set('widgets' , List(action.ids));
    default:
      return state
  }
}

This all seems to work quite well but I now have a requirement that I am struggling to fit into this model...

For UI purposes I need to have the current selection have an x/y property which is reflective of the upper left corner of the currently selected widgets x/y co-ordinates. An example state might look like...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    x: 100,
    y: 200,
    widgets: [ "widget_1", "widget_3" ]
  }
}

The logic here is fairly simple, e.g. find the min() of all the selected widgets x and y values but I am unsure of how I should handle this in terms of reducer composition as the currentlySelectedWidgets reducer has no access to the widgets part of the state tree. I have considered...

  • merging the reducers into one reducer: this doesn't seem like a great scalable solution.
  • passing the current list of widgets around with the action: this seems particularly nasty.
  • finding a better way to model the state tree: I've had a think but any alternative versions seem to have other drawbacks.
  • Building a custom combineReducers() that can feed the widgets list into my currentlySelectedWidgets() reducer as an additional argument: I think this might be my best bet.

I am keen to hear any other suggestions on how others are managing similar situations, it seems like managing a "current selection" must be a common state problem that others must have had to solve.

I am working on a React/Redux application that allows for "widgets" to be added to a page and manipulated in 2D space. It is a requirement that multiple widgets can be selected and manipulated at once. A simplified version of my current state tree looks something like the following...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    widgets: [ "widget_1", "widget_3" ]
  }
}

I currently have this tree managed by 2 reducers one managing widgets state and the other managing selection state. The selection state reducer can be simplified as (note: I am using Immutable.js too)...

currentlySelectedWidgets(state = EMPTY_SELECTION, action) {
  switch (action.type) {
    case SET_SELECTION:
      return state.set('widgets' , List(action.ids));
    default:
      return state
  }
}

This all seems to work quite well but I now have a requirement that I am struggling to fit into this model...

For UI purposes I need to have the current selection have an x/y property which is reflective of the upper left corner of the currently selected widgets x/y co-ordinates. An example state might look like...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    x: 100,
    y: 200,
    widgets: [ "widget_1", "widget_3" ]
  }
}

The logic here is fairly simple, e.g. find the min() of all the selected widgets x and y values but I am unsure of how I should handle this in terms of reducer composition as the currentlySelectedWidgets reducer has no access to the widgets part of the state tree. I have considered...

  • merging the reducers into one reducer: this doesn't seem like a great scalable solution.
  • passing the current list of widgets around with the action: this seems particularly nasty.
  • finding a better way to model the state tree: I've had a think but any alternative versions seem to have other drawbacks.
  • Building a custom combineReducers() that can feed the widgets list into my currentlySelectedWidgets() reducer as an additional argument: I think this might be my best bet.

I am keen to hear any other suggestions on how others are managing similar situations, it seems like managing a "current selection" must be a common state problem that others must have had to solve.

Share Improve this question asked Nov 27, 2015 at 18:05 andykentandykent 1841 silver badge8 bronze badges
Add a comment  | 

4 Answers 4

Reset to default 8

This is a good use case for Reselect:

Create a selector to access your state and return derived data.

As an example:

import { createSelector } from 'reselect'

const widgetsSelector = state => state.widgets;
const selectedWidgetsSelector = state => state.selection.widgets;

function minCoordinateSelector(widgets, selected) {
  const x_list = selected.map((widget) => {
    return widgets[widget].x;
  });

  const y_list = selected.map((widget) => {
    return widgets[widget].y;
  });

  return {
    x: Math.min(...x_list),
    y: Math.min(...y_list)
  };
}

const coordinateSelector = createSelector(
  widgetsSelector,
  selectedWidgetsSelector,
  (widgets, selected) => {
    return minCoordinateSelector(widgets, selected);
  }
);

The coordinateSelector now provides access to your min x and y properties. The above selector will only be updated when either the widgetsSelector or selectedWidgetsSelector state changes, making this a very performant way to access the properties you are after and avoids duplication in your state tree.

If you use the thunk middleware (https://github.com/gaearon/redux-thunk) you can make the SET_SELECTION action a thunk, which will allow it to read entire state before making the dispatch that will be received by your reducer.

// action creator
function setSelection(selectedWidgetId) {
    return (dispatch, getState) => {
        const {widgets} = this.getState();
        const coordinates = getSelectionCoordinates(widgets, selectedWidgetIds);

        dispatch({
            type: SET_SELECTION,
            payload: {
                widgets: selectedWidgets,
                x: coordinates.x,
                y: coordinates.y
            }
        });
}

This way you get all the information you need in your selection reducer without having to pass along the list of all widget-objects to your action.

I am using this:

CommonReducer.js

export default function commonReducer(state = initialState.doesNotMatter, payload) {
   switch (payload.type) {
     case "JOINED_TYPE": {
       let newState = Object.assign({}, state);
       return newState;
     }
     default:
       return state;
  }
}

SomeOtherReducer.js

import commonReducer from './commonReducer';

export default function someReducer(state = initialState.somePart, payload) {
       switch (payload.type) {
         case "CUSTOM_TYPE": {
           let newState = Object.assign({}, state);
           return newState;
         }
         default:{
           return commonReducer(state, payload);
         }
      }
}

You can try to use:

redux-named-reducers

Which allows you to get state anywhere in your code like so:

const localState1 = getState(reducerA.state1)
const localState2 = getState(reducerB.state2)

Or to assign external dependent state to your reducers like so:

reducerA.extState1 = reducerB.state1

Then access it like so:

getState(reduerA.extState1)

发布评论

评论列表(0)

  1. 暂无评论