How to learn React Redux

State management with Redux for React

With React it is possible to build complex user interfaces. Web applications such as Facebook, Airbnb and Netflix use React as well as Host Europe's website, from which you can download this e-book.

The more complex an application and the more data that is processed at the same time, the more demanding it becomes to manage the application data and keep track of the current status of the application. If you organize this state of the application, one speaks of State management.

The state of an application can range from a small detail to the overall status of the application. In principle, everything can be viewed as a state: If the user is logged in, which data is currently being displayed, is the menu expanded or collapsed, was text entered in a text field, the user has already seen the cookie modal, etc.

In summary, when writing a complex application, the question arises: In which Status or status is located just now the application, and how can you map this status and understand changes?

From local to global state management

We already know from previous chapters that a React component can save a local state itself. If a component state contains data that should be used by other components, this is passed on to nested subcomponents via props. In practice, this usually means that the top component within the component tree maintains access to changeable data in the local component state and forwards the values. If a state value can be changed by subcomponents, the topmost component passes an action callback as a prop, and the subcomponent can use it to change the state of the topmost component, which in turn passes these changes back to the subcomponents.

This is the usual flow of data in React: An action or event changes the status of a component, which then forwards the new value as props to other components. This is a unidirectional data exchange because - as the name suggests - the data is only passed on in one direction.

Uni-directional data exchange

However, the more complex the data structures become and the more differentiated the user interface that displays this data, the more time-consuming it is to pass this data through several component levels.

This may be tolerable for small, manageable applications, but if you want to display a complex surface, the individual sub-areas of which are rendered with different data sources, the question arises: Isn't that easier?

Here comes this global state management into play, which shows the state or the necessary data of an application independently of the local state of the components in a kind of Container and makes it available to the components.

The data is therefore no longer passed through individual component levels, but a deeply nested component can connect directly to the global state and has access to its data. This not only simplifies the flow of data, but also provides a much better overview of the current status of the application:

What is Redux?

Redux is a JavaScript library that provides a concept through which it is possible to read, change and map the state of an application. With Redux a status container (Store) that manages the global application status (State). The store also has methods that provide access to the state and with which data in the state can be changed:

  • getState => Data in the state are read
  • dispatch => Data in the state are changed
  • subscribe => Callback as soon as the state has been changed

State

The global state is saved in a simple object tree within the store and can be accessed using the getState to be read. There is only one state per application, which is the basis for what the application represents and how it behaves. The state is therefore the "Single Source of Truth”- the only source of truth. To ensure that it stays that way, the state itself is never directly manipulated, but only made available for reading.

Action

The only way to change the state is to use a action (action) with the method dispatch trigger. An action is a simple object that describes what should change. This action object must have the field "type”Whose value is a string and which represents the name of the action. In addition, the action object can be expanded individually in order to pass on further data in the action.

Be for it Action Creators written. These are simple functions whose return value is an action object:

Reducer

Actions are through Reducer processed that decide how the state of the application should change after using dispatch an action was sent to the store. Reducers ensure that the existing state object is not changed directly, but that a new state is created with the action.

Reducers are traditionally written as switch statements, but you can also achieve the same effect with simple if statements or use auxiliary tools such as the Redux Toolkit. However, all options have one thing in common: Reducers do not change the state directly, but always create a new state that is returned. Reducers are also pure functionsthat do not access external variables or call side effects and always return the same value (see also Wikipedia on pure functions).

Store

As soon as actions have been defined and a reducer has been created, the Redux Store initialize. Redux provides the method for this createStore available, which is given a reducer as an argument when called. The method then returns the store that - as shown above - the three methods getState, dispatch and subscribe ready.

Ultimately, the Redux Store is just an object with a few methods. And with dispatch further objects are sent to the store via the reducer (object with type and payload), through which a new state is created in the reducer. So we're actually just pushing simple objects with synchronous functions back and forth. This is nearly everything.

Asynchronous actions with middleware

With asynchronous data flows, however, handling with Redux is a bit more complex.

The Action Creators and Reducers described above relate to synchronous data processing that is executed directly on the client (browser) without sending network requests. In most productive applications, however, data coming from a server is processed by sending network requests to retrieve and save data between client and server.

A Redux store knows nothing about asynchronous data flows and processes every action through the reducers in sequence, just as they do with the store dispatch were sent. This means that asynchronous data processing has to take place outside the store. For this, Redux has the concept of Middleware which makes it possible to expand the store with additional functions.

A Middleware is the intermediate piece between action and reducer. A middleware makes it possible to change, expand, stop or delay an action, before this reaches the reducer. So when you send network requests to the server, for example, you first want to wait for the result and then decide whether the Redux state needs to be updated by an action. Since this is a common use case, Redux provides the middleware Redux-Thunk, which can be used for this use case and is provided directly when the store is initialized:

Redux-Thunk does the following: If an action is dispatched to the store that returns a simple action object, then this action is forwarded directly to the reducer. However, if an action is dispatched that does not return an object, but rather another function, then this returned function is made with the two redux store methods getState and dispatch called. This makes it possible to execute asynchronous actions or also thunk actions that access the state and the method dispatch to have.

So we got to know the fundamental concepts of Redux: Store, State, Actions, Reducer, Middleware and asynchronous actions.

Redux DevTools

To visualize what the state looks like and which actions have been sent to the store, you can install the Redux DevTools. The associated browser extension helps with debugging and state monitoring and enables state changes to be reversed (time travel). How to install the DevTools is explained on the official website.

Here is a sample view of the DevTools browser extension:

Redux Toolkit

Redux particularly recommends beginners to use the Redux Toolkit library if Redux is to be integrated into an application. Redux Toolkit provides various functions that simplify the writing of actions and reducers and the creation of a store. Without a theoretical background, however, the Redux Toolkit is not immediately accessible either, which is why it is not dealt with in this article.

installation

To use Redux in a React application, the following node packages are required, which are installed via the command line:

or

Redux-Thunk is installed as middleware so that the application can work with asynchronous actions.

Redux can be integrated in a wide variety of JavaScript applications and is not limited to the use of React. That is why there is an official React binding for implementing Redux in a React application: React Redux. This library contains React components that can be used in a React application in order to simplify the use of Redux significantly.

Creation of state structure and store

As soon as the packages are installed, one should think about the state structure. How should the state object tree be set up so that the data structure of the application is sensibly divided? To extend the examples from the previous sections, a simple state shape could look like this:

This state is very straightforward and should only serve as an example here. In a productive application, a Redux State would be much more extensive.

So we are building a simple application in which user data can be created and a product and its quantity can be stored in a shopping cart. For this we will create two reducers: userReducer and basketReducerwho are responsible for the corresponding part of the state.

The UserReducer and Actions:

REDUCER:

ACTIONS:

The BasketReducer and Actions:

REDUCER:

ACTIONS:

With the method combineReducers Redux allows you to assign several reducers for different state object trees. Once this has been done, the Redux Store can use the createStore to be created:

Pass the store on to React components

With React Redux the component Provider integrated, which makes the Redux store available for the rest of the application. The provider component is integrated at the top level of the component tree:

Provider:

Via this provider component, all sub-components that are integrated in the provider component now have automagically Access to the Redux store via the context interface of React.

Pass the store methods on to React components

With React-Redux, your own React components never have to access the store directly. Done this connect from React-Redux for us.

Connect is a higher-order component, a function that is given a React component and returns a new React component. With connect a new component connected to the Redux Store is created, which is based on Redux Store methods such as dispatch and getState Has access.

The transferred React component is not used by connect changed, but expanded with relevant props. This gives the React component the option to read the data relevant to it from the state and to change the state using actions.

Connect accepts four different parameters, all of which are optional:

For this article we will look at the first two parameters, as these are relevant for the connection to the Redux store. Further information on the other parameters can be found on the official documentation page of React-Redux.

mapStateToProps

The mapStateToProps-Parameter is a function that is called every time the state has changed. Has been connect With mapStateToProps called, connects to connect component created with the Redux store using the method store.subscribe (). If the state changes, mapStateToProps called with the state (store.getState ()). The result of mapStateToProps is an object that is added to the wrapped component as an extended prop. Only if they get through mapStateToProps If the state values ​​returned have changed, the connected component is re-rendered with the updated props.

MapStateToProps accepts two parameters, the second being optional:

Will the mapStateToPropsFunction declared with only one parameter, it is always called when the redux state changes. In this case, the first argument is the entire Redux state or the result of store.getState ():

Will the mapStateToPropsFunction is declared with two parameters, it is still called whenever the redux state changes or if the props of the parent component change, which are shown here as ownProps to the connected component of connect be passed on.

mapDispatchToProps

As a second argument of connect becomes mapDispatchToProps declares what is relevant for dispatching actions to the store.

Becomes mapDispatchToProps passed as an object, each field in the object is treated as an Action Creator and in the connected component of connect by default with store.dispatch called. This means that you can use the already defined Action Creators in mapDispatchToProps can specify, and connect takes care that the connected component is already using this action dispatch is provided as a prop.

Note: Don't specify action creators mapDispatchToProps further, but simple action objects, or would you also like to use mapDispatchToProps on ownProps (the transferred props of the parent component) have to be accessed mapStateToProps Declare as a function and not as an object. The official documentation, however, recommends object notation with Action Creators. If this is not sufficient for the reasons mentioned, further information is available on the official React-Redux website.

We saw how you can use React-Redux to connect individual React components to the Redux store. Through the provider component, the Redux store is passed on to lower components via the React context. With the function connect this store is accessed in the React context and, if mapStateToProps and or mapDispatchToProps defined, passed on as props to the wrapped React component. This means that the actual React component does not have to communicate directly with the Redux store, since connect Taking care of the connection “under the hood”.

Example user component

In order to use the user state already defined above first name, lastName and age and pass the associated Action Creators to React components, we will now define some components that render a simple input form. This input form reads data from the Redux state and changes data in the Redux state with every text entry.

First, we write a simple input field as a React component that calls the value every time a field is entered. This component is not connected to the Redux store:

We can now use this input field for the three user state values: first name, lastName and age. For this we write a user component that renders this input field three times and that with the Redux store through the function connect With mapStateToProps and mapDispatchToProps connected is:

The values ​​of the input fields are taken from the Redux state state.user read and changed each time text is entered by the actions, which results in a new rendering of the components with the updated state value.

Example basket component

We proceed in a similar way if we want to connect components with the basket state.
First, we render a list of sample products that the user can choose from.

For this we write a component that takes care of the rendering of a single product.

This component gets the prop “name” from its parent component, which is in mapStateToProps as ownProp is passed on. In addition, in mapStateToProps checks whether this component has just been selected in the state by entering the name via the ownProps with the current value in state.basket is compared.
The relevant action to select the product can be found in mapDispatchToProps be passed on. As soon as a product is selected, the corresponding product name is in the Redux state state.basket.productName set and the component will be re-rendered accordingly because it has now been selected (isSelected === true).

In order to show the user which product name is currently selected, we write another component that only takes care of this display by filling out the name state.basket read:

Even if the component "SelectedProduct”When it comes to a very small component, it is generally okay to use different components connect or to reconnect nested components directly to the store. This is not a loss of performance, as was partly assumed some time ago. This is described in more detail on the official Redux documentation page.

With this architecture, the parent component of the basket does not have to be connected to the Redux store, since the child components are already “smart enough” and look at the state themselves or change it. However, the individual product components expect a "name" prop from the parent component:


To select the number of products, we create a simple component that is also connected to the Redux store and the current ones amount reads from the basket state and with the two actions incrementAmount and decrementAmount can update this value:

React-Redux with hooks

Since version 7.1.0 of React-Redux there is a hook interface that is an alternative to the existing connectFunction can be used. With these new hooks it is now possible to connect a React component directly to the Redux store without first having to use it connect to call. This also makes defining mapStateToProps and mapDispatchToProps obsolete and reduces some of the code.

useSelector

With the useSelector-Hook it is possible to read data from the Redux state. Thus the selector behaves similarly to mapStateToProps of the connect-Function. A selector hook is called with the entire redux state and this every time the component is re-rendered and when the redux state changes due to the dispatch of an action. The selector compares whether the referenced state value has changed and only then forces a new rendering. Since the selector is declared directly in the React component and can therefore be called multiple times, it must be ensured that the selector function has a pure function is. This means that the function should not hold an internal state or call side effects and always return the same value (the state value that is read).

For comparison, here is a simple component with connect and mapStateToProps:

and with hooks:

useDispatch

The useDispatch-Hook gives a reference to the store.dispatch () -Method and can therefore be integrated directly into a React component. In contrast to the useSelector-Hook becomes the reference too dispatch only created once and no need to worry about re-rendering.

For comparison, here is a simple component with connect and mapDispatchToProps:

and with hooks:

Example basket component with hooks

To illustrate, we are rebuilding two components with the hooks from React-Redux.

The component “Product” with connect:

and rebuilt with useSelector and useDispatch:

The component “SelectedProdukt” with connect:

and rebuilt with useSelector:

The parent component can remain as it was before, as it is not connected to the Redux store even after the renovation:

Asynchronous action with Redux thunk

Since we already integrated the Redux-Thunk middleware when creating the store, it is possible to send asynchronous actions or thunk actions to the store.

To illustrate this, we will create another component that renders a submit button and when onClick-Handler dispatched an asynchronous action:

As already described above, the Redux-Thunk middleware ensures that dispatch is called with an Action Creator, which does not return an Action object, but a function that then recognizes this function as a thunk action and uses it dispatch and getState is called. This makes it possible to access the current state within this thunk action and other actions with dispatch to call. In our example we create the thunk action “submit”, which reads the user data and the basket data from the Redux state and sends them to a server fetch sends.

State management with Redux for React - conclusion

With the integration of Redux into a React application, you get a powerful tool to organize the application state. Even if there are a number of concepts that you have to learn at the beginning and therefore access is not easy, especially for beginners, it is worthwhile in the long term to work with the global state management from Redux in order to make an application clearer.

The direct connection of a React component with the global Redux state prevents different props from being passed through several component levels and thus makes individual components independent of their nesting in the component tree.

Global state management with Redux requires a lot of boilerplate to implement the Redux store with actions and reducers. But once you have internalized the Redux concept, that too is easy to do. With the installation of Redux DevTools it should seem easier, especially for beginners, to understand how the Redux flow works with Actions, Reducers and State. In addition, while this chapter was being written, the official Redux documentation was updated with a focus on the Redux toolkit, which should also reduce the hurdle for beginners.

More articles on the topic:

Lisa Oppermann is a freelance JavaScript developer with a focus on React and React-Native. She works for various customers from the fin-tech to the smart mobility industry and has introduced Redux in various applications, among other things.