Derived state for frontend applications comes up a lot. State
B depends on State
A, so after you update
A, by the time you read
B you want it to be updated based on the latest value of
A. There’s a bunch of ways to model this, involving different tradeoffs:
A popular solution is selector libraries like Reselect. The way they work is:
Sthat takes State
Aand returns State
B, you call
To make this pattern work efficiently,
S of course has to be memoized, and State
A immutable for that memoization to work. This approach makes the following tradeoffs:
Undux makes a different set of choices when it comes to derived data.
With Undux, you treat derived data the same as non-derived data, both from the store’s point of view and your user’s point of view. That’s because from your Component’s point of view, the two are the same. In fact, if your component (or its container) knows what’s derived and what isn’t, that might be a sign of poor encapsulation.
To derive data in Undux, you just compute it with an effect. Optionally, prefix its key with something:
store .on('networkData') .pipe( map(postProcessData) ) .subscribe(store.set('derived/processedNetworkData'))
This approach makes different tradeoffs than selector libraries do:
derived/, if you want)
store .on('networkData') .pipe( debounceTime(200), filter(_ => _ !== null), map(postProcessData) ) .subscribe(store.set('derived/processedNetworkData'))
Why does Undux do it this way? Isn’t there something holy about a clean source of truth?
My view is there isn’t. Fundamentally, all data is derived. Even the list of users you got back from a GraphQL endpoint – that’s a function of two things:
users = F(initialState, API request)
Or, a count of how many times you pressed a button. That’s derived from:
count = F(initialCount, Stream(clicks))
That is, users are derived from an initial state and a network request. And a click count is derived from an initial count and a stream of clicks.
And when you do, you realize it makes life easier to forget about drawing lines about where state comes from, and letting components interact with all state the same way. It’s a simpler mental model, it’s easier to reason about, it’s better enapsulation, and with Undux, is more powerful too.
What do you think?