State Management with React Hooks

This post was originally published on Justgeek.it. 
 
Facebook itself came out with a proposal to solve this problem, presenting the Flux architecture, which is a pattern of writing the code rather than a full-fledged framework. Thanks to the use of one-way data flow, it allows for its precise tracking and analysis, and thus definitely facilitates working with the code and its subsequent maintenance.

Its full-fledged API implementation that has been officially supported by the creator of Flux is Redux. A small framework (~ 2kB including dependencies). Although most often associated with React, there are no contraindications for using it in tandem with any other library for creating UI.

Redux

Redux stores the state of the entire application in a tree structure of objects in the so-called store. Thanks to a single store, our Single Source of Truth of the entire application, we are able to easily debug and view the entire data flow. All changes are made only with pure functions because in order to block the store mutation by side-effects this is configured to be "read-only".

Actions, Types, and Payload

An action is a simple object whose task is to describe what happened in the application. It is through dispatch of action that we inform about a change in the status of the application. Each action should be described by a type string that describes what the action is for.

dispatch({ type: 'INCREMENT' });
dispatch({ type: 'SET_COUNTER', counter: 1 });

Reducers

To link the state with actions we create reducers. It is nothing more than functions that respond to every dispatched action. They take two arguments: state and action. They do not directly modify the state, but only return an updated copy.

function counterReducer(state = [], action) {
switch(action.type){
case 'INCREMENT':
return {
...state,
counter: state.counter += 1,
}
case 'SET_COUNTER':
return {
...state,
counter: action.counter,
}
default:
return state;
}
}

Although the whole Redux idea uses only plain JavaScript, its integration with a given library or framework will require the use of a specific Redux API, e.g. ng-redux or react-redux. Can we avoid it somehow? In React, yes.

Redux without Redux?

React Hooks

Hooks presented in React 16.8. finally allowed users to create functional components without worrying about managing their state. To be able to use state and lifecycle methods, we no longer need to create a class with ECMAScript 6. The hook is a simple function whose parameter is the initial value, and which returns a list of two elements: the first is the value, and the second function allows you to change this value. A small thing that gives a lot of possibilities. 

const [counter, setCounter] = useState(initialCounter);

const increment = () => setCounter(counter + 1);

increment();

State management - how to use Redux with React Hooks?

So how do you use hooks to create Redux API? This time, let's reverse the order and start with the reducer.

function counterReducer(state = [], action) {
switch (action.type) {
case 'INCREMENT':
return [...state, {
counter: state.counter += 1,
}];
case 'SET_COUNTER':
return [...state, {
counter: action.counter,
}];
default:
return state;
}
}

The reducer above is almost identical to the one not intended for cooperation with hooks. The only difference between them is the format in which they return data. In the first case, it is just a state, while in this list it has two elements. Let's create something like Redux API.

function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);

function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}

return [state, dispatch];
}

The userReducer function is nothing more than a custom hook, thanks to which we gain access to the state and a function that allows you to change it. Of course, before this happens, the data is directed to the reducer. 

const [counter, dispatch] = useReducer(counterReducer, []);

function handleCounterSet(counter) {
dispatch({ type: 'SET_COUNTER', counter });
}

handleCounterSet(1);

The React creators predicted that managing data-flow of our application using reducers is a frequent case and provided us with a custom hook called useReducer.

const [state, dispatch] = useReducer(reducer, initialArg, init);

It contains all the logic presented earlier, so there is no need to write it completely from scratch.

Gains and losses of state management with React Hooks

By using React Hooks, we can create our own Redux implementation in our application. We don't have to download it and add additional dependencies to the project, but will such a tool always be the best choice? Probably not. We reduce the bundle by several kilobytes, but we lose all integration even with such a tool as Redux DevTools. So, as always, it depends on the programmer's design and imagination. So I leave the choice of technology to you.

Like what our developers do? Join our frontend team! We’re looking for software developers - check our job offers!  

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .