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 mycurrentlySelectedWidgets()
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 mycurrentlySelectedWidgets()
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 badges4 Answers
Reset to default 8This 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)