getDerivedStateFromState — making complex things simpler
I love React. I love React for the way it behaves. It does not tries just to solve the problems — it tries to solve them right.
HOC, RenderProps, Composition, Components, Stateless, Stateful, twitts from Dan and community, discussions, solutions, problems and findings.
On last birthday party we got a brand new way to handle props change — static getDerivedStateFromProps
. Then Shitstorm. Then dozen acticles about how to use it. Then dozen issues about how someone could not use it.
JFYI:
getDerivedStateFromProps
, just defines how having StateA and Props form StateB. And it shall be a single one way to transit.
The problem is simple sometimes you have to form a state based on State you have and props. Sometimes it does also matters which props you had.
There are million cases then you need it. There is a discussion about refactoring from componentWillReceiveProps
to gDSFP
. There is a PR to “fix” it. And it was rejected. And it shall be, because they forgot about one use case.
[Test|Behavior] Driven Development
For a better understanding lets create a real word issue we are going to solve, and solve it. And lets it will be simple task — display a table.
Display a table with pagination and sorting.
- What one shall do to render that table?
- get table data
- sort
- pick some range within table data
- display it.
2. What do if data got changed?
- Start everything from a scratch
3. How to change a page?
- this.setState({page: newPage});
4. How to change sorting?
- this.setState({sorting: newSorting});
6. How to react to state change?
- Have no idea. Seriously!
6. Could I use props not the state?
- Sure. Create a “smart” component, which will control a “dumb” one.
- That “dumb” one could get everything it need as props, and use
gDSFP
to actually sortdata
it will get as prop, withsort
settings it could read from props, and store a slice frompage
topage+1
in the state. - And each time you might change any prop, it will do everything from a scratch, meanwhile sorting is not a fast thing.
7. So could I use state?
- No, you can’t. Cos you have to react on state change, ie then someone changes
page
— it should getstate.sortedData
, generate a data slice and store asstate.result
. But there is no way to react to state change. There is no way togetDeviredStateFromState
, even if this article was named after it.
8. No way to solve a puzzle?
- Mmmmm… Lets talk about it.
Solving the puzzle
State machine way
This is complex. Finite-state automaton defines how one state transit to another.
- When you are in
idle
, and recievepage change
, transit torecalculation state
. - On
recalculation state
enter — recalculate value, transit toidle
state.
You might need define a calculation state
per each possible change to optimize CPU usage, and that is how this should work. Underneath. Deep deep inside. Better — on whiteboard.
React way
Current official way to solve this puzzle, is to use memoization in render.
It the same time (link):
One solution suggested memoizing that computation and calling it each time, which is a good idea but in practice it means managing caches which, when you’re dealing with a function that takes more than one argument, greatly increases your surface area for potential bugs and mistakes. I’m having to resort to a weird multi-depth
WeakMap
, and making decisions about when to drop different levels of the cache. Ben Styles
Let me rephrase this — BUGS, TECH DEBT, END OF UNIVERSE.
Meanwhile I wish a quite simple thing — simple, stable, bug-free solution. I am not asking for anything more. Only for this.
Ok. HOW?!
To solve this task, and any task like this, and any task not like this, but similar to this, you might need only 2 things. And you know both of them.
1. Thing number 1 — Waterfall.
I know that waterfall suck, agile rule. But what do you know about it? The first line in wikipedia contains everything we need:
The waterfall model is a relatively linear sequential design.
It describes how one follows another. Predictable, imperative approach.
First — sort. Second — slice. Third — render.
You can define “the way things work” once, and it always be the same.
2. Thing number 2 — Reselect.
Reselect is selection and memoization library. And lots of people prefer it among other memoization libraries for cascades.
Having 2 memoized values, how to form final memoized value. (easy)
BUT
But I could agree with Ben — cascading memoization functions one after another, “managing caches which, when you’re dealing with a function that takes more than one argument, greatly increases your surface area for potential bugs and mistakes”.
Reselect cascades are super powerfull things, but it is not easy to use them, as long you have to “extract” all the “real” variables each step depends on. Hard, senseless and fragile moment.
And thing number 3, “The Flow”, can solve this.
The Flow
The flow, memoization composition, literally composed
list of memoized functions. You can define how to form a final state, and execute the sequence.
This is much better that unstructured, even chaotic calling to some functions in random order, or reacting to state/prop change, as long you can not predict the order of changes, and result could be indeterminate.
But I don’t like how it looks.
- You have soiled a render function.
- You could not perform side effects, like fetching data for a selected page. Cos render have to be pure. And if not, oh, something bad might happen.
- Some props are concreted into flow definition, meanwhile some — are not.
There must be a better way!
The same, but a bit better way
It looks almost the same as lodash version, but behaves differently:
- you provide a
input
input
will be used to call the first function.output
of the first function will be merged back, and used to call the second function.- And result of a second step will be used as result.
And each step knew which values it will read from input
, thus knew on which changes in input
which step in flow
should be recalculated. This is not the magic, this is memoize-state
, and power of Proxies (works in IE11)
You are getting data from “single source of truth”, and returning it back. And all the calculations are made inside getDerivedStateFromProps
. For MemoizedState is a “smart” component “you sure could use” from above, just a but more smart than usual.
Why this is a solution?
- You can form
input
from any data. Merge you state and your props together. - You can use as much steps in
flow
as you need, and each one will react only to the changes of observed keys, or results of previous steps, if that keys was formed in previous steps. And each next could not use data from previous state. Just data that exists in state, no mater of origin. - The execution “order” is always the same. It is super hard to made a mistake. And super easier to predict.
- You can run side-effects.
Yes — you can run side-effects. You can define a step in flow, to be triggered on page(for example) change, and setState
something. If you will change not page, but something else — you will not run side-effect, cos call arguments were unchanged — that is the main principle of memoization function.
More proofs?
Here is full-cream example to play with. Looking simple, and it IS simple. But solving a complex task.
Yet again: form input, run again of sequence, rerun “right” steps on data change.
So, what you just proposed?
I am proposing to utilize the power of react-memoize
, to be more concrete — MemoizedFlow
component.
theKashey/react-memoize
react-memoize - Don't forget to cache your {props|context|state}. 🤞
github.com
The magic memoization, only memoize-state could provide. You have to try.
MemoizedFlow is that getDerivedStateFromState nobody adds, and is that getDerivedStateFromProps, you might need.
What it does not solve
There is no prevProps
. You can copy them into the state (as the last step of flow?) but you probably shall not “want” it. You might want to have access to the last derived
state, and you might even have it.
<MemoizedFlow
input={this.state}
flow={[
({stateVariable}) => ...
({oldState}) => ...
({oldState,...rest}) => this.setState({oldState: rest})
]}
>
But it will cause infinite loop, as long you are changing the variables you are depend on. But not having some superpowers might be a good.
Not giving you ability to create a VERY complex algorithm, asking you to making things simpler, might be the good.
“The memoized Flow” is about making complex things simpler. Nothing more.
More about “problems” this library (and this article) could solve: