The Flow

getDerivedStateFromState — making complex things simpler

Anton Korzunov
ITNEXT
7 min readApr 15, 2018

--

Click here to share this article on LinkedIn »

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.

  1. 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 sort data it will get as prop, with sort settings it could read from props, and store a slice from page to page+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 get state.sortedData, generate a data slice and store as state.result. But there is no way to react to state change. There is no way to getDeviredStateFromState, 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 recieve page change, transit to recalculation state.
  • On recalculation state enter — recalculate value, transit to idle 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 ainput
  • 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?

  1. You can form input from any data. Merge you state and your props together.
  2. 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.
  3. The execution “order” is always the same. It is super hard to made a mistake. And super easier to predict.
  4. 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.

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:

--

--