I have a React/Redux app that displays markers on a map, and groups them into clusters based on their proximity given the current zoom level (similar to this example - my app differs from this example slightly in that the clusters on the map will display specific data from their child markers, and not simply a total number of child markers).
I anticipate having new markers added frequently, and the app will update to show the new markers in real-time.
The React component structure looks something like this:
<Map>
<Cluster markerIds=[...] />
...
</Map>
So the map re-renders the appropriate clusters every time a) new markers are added to the map, and b) the map's zoom level changes.
I'm trying to determine the best way to organize this data in a Redux state tree.
One option would be to keep the state tree very simple, and let the UI handle all of the clustering logic as needed:
{
markers: {
1: { name: 'Bob', location: [-40.08, 37.62] },
2: { name: 'Steve', location: [51.08, -25.62] },
...
}
}
If I organize state in this way, the UI will have to go through every marker and recalculate the visible clusters every time the zoom level changes. With a high number of markers, that could turn out to be a lot of recalculations, and I anticipate that users will be doing a lot of zooming in and out while using this app.
Another option would be to keep the cluster organization in the state tree for each zoom level (e.g. 1 through 19):
{
markers: {
1: { name: 'Bob', location: [-40.08, 37.62] },
2: { name: 'Steve', location: [51.08, -25.62] },
...
},
clustersByZoomLevel: {
1: {
clusters: [
{
clusterLocation: [22.59, -21.54],
markers: [2, 11, 4]
},
...
]
},
...2-19: {...}
}
}
In this case, the cluster calculations would only happen when new markers are added to the map, and would only be run on the new markers, not on the entire set. Zooming in or out would not require recalculating, as the cluster organization would already be stored in the state.
So, which option makes more sense?
On the one hand, I am compelled to keep it simple and avoid premature optimization (i.e. go with Option 1).
On the other hand, I can see that Option 1 could easily result in tons of recalculations happening very frequently. And Option 2 provides a state tree that translates more directly into my React component structure.
Which option would you suggest, and why? Are their other approaches I'm not considering that may work better?
From what you're asking, both are viable.
Due to the nature of your exact question (depending upon the scale), I would say that a caching layer is totally viable (so long as the pure data-set remains there).
In terms of an abstract set of thoughts that my team operates by:
if it can be trivially derived, do it in the transformer which is fed to connect( transform, bind )( Widget );
.
If it can't be trivially derived, then caching it makes a lot of sense.
Great examples of this are where you may have 8000 raw product results, and then filter sliders, which operate on all sorts of data within a result.
You are going to want to keep a list of filteredResults so that you don't have to keep recalculating that on the fly.
Changing the page number of the view, though, shouldn't require rerunning all of those expensive filter operations (ie: in the transform), but rather just choose which view subset is sliced from the list of all already-filtered results.
It's one step further than what you're asking, but my one other suggestion would be "organizing by UI": be careful that you rotate common data out of "page" or "api" specific branches of the tree, and move them to a common place, if they are indeed common things.
Having to mutate an object in two different places, to stay in sync with one another would be painful.
{
searchPage: { user },
landingPage: { user },
checkoutPage: { user }
}
To match the UI is going to be very painful.
So... transform what you can sanely derive, cache what you can't, rotate what's common to be closer to the root.
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