In April, I was lucky to attend a very cool event -
React Amsterdam . In addition to pleasant organizational moments, there were also many interesting reports. They were mostly applied. Since the technology stack was basically settled, the speakers talked about how to solve practical problems, rather than promoting something unfamiliar and revolutionary. Here I will talk more about the performance of “
setState Machine ” by
Michele Bertoli from Facebook.
The main problem the report was devoted to is the difficulty in managing the state in React.
For example, let's implement the familiar functionality of all - Infinite Scroll, that is, loading data when the user scrolls to the end (or almost to the end) of the page. There are many useful packages that solve this problem, but often you have to write on your own.
What we need to do for this in our component:
')
- Add a handler for the
scroll
event. - Add a check to see if the user has scrolled to the desired place from which we will load the data.
- Actually, load the data.
In the first approximation, this is enough, but let's add a few more requirements for proper operation:
- Do not load new data if the previous batch is still loading.
- Somehow inform the user that the data is loading - show loading or something like that at the time of loading.
- Do not start loading data if everything is already loaded.
- Add error display to user.
Let's imagine what needs to be stored in our state, besides the data, in order for our functionality to work correctly:
- The
isFetching
flag shows us that data is being loaded. - The
error
field should contain error information. - The field
isEmpty
- shows us that there is no data. - If suddenly we want to add functionality for
retry
, then we need to store information for it retry
.
What are the main disadvantages of such an implementation:
- Great context binding. There are a lot of conditions, for example, we load data only when we scroll to the right place, while previous data are not loaded, and so on.
- Our code is difficult to read and understand.
- It is difficult to scale - when adding a new property to the state, you need to go through all our conditions and understand how to change the state in one place or another so as not to break the logic. This can also lead to bugs.
The
state machine (State Machine) will help us fix all these shortcomings.
In essence, this is the principle of using the finite state machine (Final State Machine), an abstract model containing a finite number of states.
The model is described using five parameters:
- All states in which the automaton may be located.
- A set of all input data accepted by the automaton.
- The transition function - takes the previous state and the set of input data, returns the new state.
- Initial state
- The final state.
Only one state can be active at a time.
Accordingly, we can determine the conditions for the transition from one state to another.
As an example, consider the work of a traffic light. This is a machine with three states, we know their sequence, and we can also conditionally name the initial and final state.
Let's add the
react-automata library to our code — an abstraction of the state machine for React. This is a wrapper over another
xstate library -
stateless stateful JS state machines and state diagrams.
To understand how this theory applies to our case, let's see how the functionality will look like a
statechart :
To begin with, we indicate the initial state — the entry point, which is the addition of the scroll event. When we are ready for further work, we send the machine a READY event, and the machine goes into the download state. If the download was successful and not all data has been loaded yet, we are in the listening state. When scrolling satisfies the condition of loading a new portion of data, we move to the loading state and can remain in this cycle until we load all the data. Then we no longer listen to the event.
Schematically, our code might look like this:
import React from "react"; import { hot } from "react-hot-loader"; import { Action, withStatechart } from "react-automata"; export const statechart = {
We use the react-automata hoc
withStatechart
, transfer our initial data, and now the
transition
method is available in props to change the state of the machine, and
machineState
is the current state of the machine.
The
statechart
variable is a programmatic description of our drawing.
Advantages of the approach:
- Less bugs.
- Easier to read and understand the code.
- Separation of what happened and when it happened. The first is controlled by the component, the second by state diagrams.
Useful links:
- Michele Bertoli's report on React Amsterdam 2018: https://www.youtube.com/watch?v=smBND2pwdUE&t=3137s
- React Automata: https://github.com/MicheleBertoli/react-automata
- Xstate documentation: http://davidkpiano.imtqy.com/xstate/docs/#/
- Explanation of state diagrams: http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf