State Management with React Hooks: the best solution

南小北
6 min readAug 1, 2019

--

1. Traditional React State Management

When it comes to React state management, many people don’t want to mention it.

Redux: Player 1, the most popular player, used with react-redux. Don’t handle the asynchronous logic, so redux-thunk and redux-saga often hear are its subordinates. Its characteristic is difficult to use. If use it according to the official docs, basically want to die.

Mobx: Player 2, using the way of monitoring, runs counter to React’s thinking, it is wrong, hooligans!

dva: Redux encapsulation, using redux-saga, generator syntax, want to die.

Rematch: Redux encapsulation, similar to dva, model is divided into reducers and effects, not concise enough.

Advertising: So 🐤 Retalk is the simplest and simplest Redux encapsulation ever. Don’t miss it:

2. React Hooks is coming

Anyway, Hooks is coming. The former state management is used for the former development methods. In order to express Hooks a little, they add a few useBlablabla() to finish it.

Compared with the lightness of Hooks, they are heavy, old-fashioned and perfunctory.

Is there a state management system dedicated to Hooks? At present, there is no popular expectation. Most of them are veteran athletes who take part-time jobs, which is somewhat inconsistent.

Not revolutionary enough.

3. Why do we need state management?

One is to solve the communication problem of adjacent components.

Although it can be solved by “Lifting State Up”, but there are two problems:

  1. Every time a child component is updated, it triggers an update of the parent component responsible for the download state (also the problem on Context), and then writes a bunch of PureComponent, shouldComponentUpdate, React.memo. Is the code still readable? The dregs in React design are enough and tragic.
  2. If more logic is written in the parent component, can the code still be seen? No consideration is given to the feelings of the parent component.

So “Lifting State Up” is not a good idea, we still needs state management.

In addition, one of the most important benefits of state management is — —

It can extract the logic of “interacting with servers” and “manipulating data” from components, which only need to receive the processed data. This is equivalent to the separation of a Service layer out.

The greatest benefit is the separation of code logic, the responsibility of each, the clear structure, and the component to do what the component should do.

So we need state management.

4. What kind of state management do we need?

If you start with Redux, you will find that it provides a global store. Then what? Then it didn’t say anything. If there were any problems, solve them yourself. I went to bed first.

In actual business development, the most common scenario encountered is the need to distinguish between different “modules”, which is the most basic requirement.

Vuex is pragmatic and provides the concept of modules.

What about Redux? Of course, it’s up to you. We can just provide an idea. You can do something as simple as that.

If API design is not standardized, it will inevitably lead to numerous different implementations.

CHAOS.

What kind of state management do we need?

We need to be able to divide different modules, modules are independent, and modules can communicate freely.

Many state management libraries only consider the “independence” of modules, but it is unfriendly to keep silent about “communication”.

Independent and connected — that’s what modules mean.

5. How to divide modules?

It is recommended to divide different modules according to the basic routing entries, which is also the idea in dva (its folders are simply called routes).

This is also in line with the natural way of thinking, the division of routing, that is, the natural recognition that they belong to different modules.

Each state management module, which we call “model”, is proposed to be bound to a routing entry component. Each entry component and its sub-components correspond to a model as a whole.

---A    index.jsx    model.js---B    index.jsx    model.js---C    index.jsx    model.js

6. How do modules communicate?

In components, can get own model and other models, which is not difficult.

Mainly, within the model, each method needs to call each other, at the same time, a model needs to get the state and methods of other models, which is the meaning of communication.

It’s simple enough.

This is a good and thorough design.

7. How to get models in the Hooks component?

Most directly, we want to access a model through Hooks in the component.

const { someStateInA, someActionInA } = useModel(a);
// or
const { someStateInA, someActionInA, someStateInB, someActionInB } = useModels(a, b);

The duplicate code useModel() passes in a model, which model you need, you have to import the model file into the component, which is not free enough to rely on the need to import the file.

As long as the operation is troublesome and the code is written more, it is not a good design.

In addition, Hooks is to make the code clearer. We can’t violate the design philosophy of the whole system like Mobx.

So model acquisition is best separated, and the way of useModels(a, b)introducing multiple models at one time is not clear enough.

Can’t rely on files, introduce them clearly, so:

const { someStateInA, someActionInA } = useModel('a');
const { someStateInB, someActionInB } = useModel('b');

Relies on a string, so no need to import files in each component. useModel() can only access one model, and the code is clear enough.

This is the design we need.

8. How to design the model structure?

A model can be divided into two parts: data (state) and methods of manipulating data (reducers, effects).

reducers and effects, or mutations and actions in Vuex, are designed to distinguish between direct and asynchronous operations, synchronous and asynchronous functions. Not concise enough, can you merge into one?

The answer is yes. We call it actions.

The method of updating state is injected directly into the model, so an action can be synchronous or asynchronous, it is just called as a function.

So, divide the model into two parts.

exports default {
state: {},
actions: {},
}

9. How to design model communication?

Model communication is mainly about accessing other things in action.

Free and unlimited communication.

An action needs access to the following: 1. own state, 2. own actions, 3. other models’ state, 4. other models’ actions, 5. own state updater

To simplify, need access: 1. own model, 2. other models, 3. own state updater

Simplify it again: 1. model, 2. own state updater

So only need two methods: getModel() and setState().

getModel() for own model, getModel('other') for other models.

How to get these two methods?

  1. Access through this. But we are designing state management for Hooks. Hooks is to abandon this, or that sentence — it can’t violate the philosophy of system design.
  2. In Vuex, context is injected into the first parameter of a function, which is counter-intuitive when invoked and abandoned.
  3. Import methods to the model is too troublesome. As long as the operation is troublesome and the code is written more, it is not a good design.

So the only way (also see the design in Rematch) is to inject methods to parameters.

That is, to turn actions into a function:

exports default {
state: {},
actions: ({ getModel, setState }) => ({
someAction() {
const { someState, someOtherAction } = getModel();
}
}),
}

The replication code here takes into account an aesthetic problem, getModel() because lots of actions will use it, and when actually written out, because the l height is ugly, so the API is simplified to model().

model() for own model, model('other') for other models.

10. What is flooks?

So 🍸 flooks, 福禄克斯, was born:

import { setModel, useModel } from 'flooks';const a = {
state: {},
actions: ({ model, setState }) => ({
someAction() {
const { someState, someOtherAction } = model();
...
setState(payload);
}
}),
}
setModel('a', a);function A() {
cosnt { someState, someAction } = useModel('a');
return (
...
)
}

Natural modular support.

There are only two APIs, setModel() and useModel().

And two parameters of actions, model() and setState(), that’s all.

It’s simple enough to achieve everything.

One more thing. Design Philosophy

Decentralization, Decentralization and Decentralization.

Everything is for simpler development.

No need for top-level distribution stores, a store folder or store.js file.

No top-level createStore() and no top-level Provider are required.

When registering a model, you can do everything in your own folder without first introducing the model into the centralized file.

It’s as simple as connecting components to models directly with setModel().

Modules, building blocks, like combination, simple, flexible, this is our philosophy.

Each component and model constitutes a self-organization, and they can access the world at the same time. They can meet in any corner of the world without having to report to the relevant authorities.

This is our philosophy: independence, freedom, individuality, the world.

--

--

No responses yet