Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux: Derived data for items in a collection

Tags:

reactjs

redux

Here is a simplified version of my state tree:

{
    "radius": 8,
    "nodes": [
        { "id": 1, "x": 10, "y": 10 },
        { "id": 2, "x": 15, "y": 10 },
        { "id": 3, "x": 20, "y": 10 }
    ]
}

Essentially, I have a list of nodes that each have an x and a y. I also have a radius number that is used to calculate which other nodes are within the radius of a node, ie, close neighbors.

I need my state to look like this:

{
    "radius": 8,
    "nodes": [
        { "id": 1, "x": 10, "y": 10, "neighbors": [2] },
        { "id": 2, "x": 15, "y": 10, "neighbors": [1, 3] },
        { "id": 3, "x": 20, "y": 10, "neighbors": [2] }
    ]
}

The calculation for the neighbors is rather expensive, so I only really want to calculate this if one of the node positions changes.

I looked into used selectors for this, but I'm not sure a selector will work. In order to derive the neighbor of a given node, I need the whole list of nodes and the radius. If I pass the whole list of nodes to the selector, the selector will recalculate if anything in the node collection has changed. I only need to recalculate if the x or y value of any node has changed. Note that these nodes have a lot of other keys, in addition to x and y.

It also seems that for selectors to work, I would need a new selector for each element of the nodes array, which is dynamic. Is this correct?

Another hurdle is that I need this list of neighbors to calculate other state in other reducers. Does this mean I should do this calculation in the nodes reducer?

Does anyone have any insight on this problem?

*EDIT

I ended up moving the neighbors into the state instead of deriving them. I needed access to the neighbors from within the reducers for certain other actions, and I was not able to find a way to cache/memoize them.

It seems a little fragile, because I'm essentially deriving and storing the neighbors on certain actions that involve them and not re-deriving them on others that don't. (That's pretty much what a memoized selector would be doing automatically...) But alas, I couldn't find a good way to do that.

Is this wrong?

*EDIT2 I ended up splitting my nodes state into two parts:

nodes: {
    nodesById: {
        "1": { "id": 1, "color": "blue", ... },
        "2": { "id": 2, "color": "red", ... },
        ...
    },
    positionsById: {
        "1": { "id": 1, "x": 0, "y": 10 },
        "2": { "id": 2, "x": 10, "y": 10 },
        ...
    }
}

This way, I am able to write a selector that takes just the positions of the nodes and the radius to compute the neighbors:

export const getNeighborsById = createSelector(
    (positionsById, radius) => positionsById,
    (positionsById, radius) => radius,
    (positionsById, radius) => {
        // calculate neighbors
    }
);

This selector will only be re-run when the positionsById changes and not when nodesById changes (which happens much more).

This solves my problem, but seems kinda wrong to maintain two lists in the same reducer, but maybe not...

like image 996
lax4mike Avatar asked Apr 19 '16 16:04

lax4mike


1 Answers

There are several common misconceptions about states in Redux:

  1. state must be described using native JS types. Wrong
  2. state represents what's shown in the screen. Partially wrong

1. Use the right data structures

According to your use case, storing your nodes into an Array does not seem to be the best option. Yes, you could do this but whenever a node is added, removed or modified you would need to walk your list and update neighbors. If you move this process to a selector, you would need some kind of memoization not to sacrifice the performance.

In my opinion your nodes should be stored in spatial-partitioning data structure (e.g. an immutable quadtree). This kind of DS would make node traversal much faster and it should not be too hard to identify neighbors.

2. Use selectors to derive your state

A selector is useful when you have to perform a complex selection on your state or if you need to derive your state into another shape before using it.

If your nodes are stored in a quardtree, it would be easy to write a selector to query its neighbors and return them as an array.

like image 175
Florent Avatar answered Sep 27 '22 17:09

Florent