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...
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.
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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With