Redux is one of the most commonly used tools for managing data flows in applications developed with React. In this article, we’ll guide you along a pathway to the release of an app with the help of Redux features.

A sophisticated methodology for an idiomatic Redux

We cannot call Redux js a simple library. It’s an ecosystem that gives developers various patterns and approaches to coding. What will you use for the asynchronous actions? Will you use promises, sagas or thunks? You won’t find one single solution and you won’t find the best solution of using React redux as well. Such a wide variety can make you confused, so I’ve decided to show you my personal approach to the use of this library. It’s easy as a pie and you can use it in a lot of cases.

It’s time to develop the first app

We need something tangible. Let’s start the app that will display the most popular Reddit posts. The first screen is a questionnaire where the user will define three most interesting categories. The next step will be to show a list of topics for these categories. The content of the post will be displayed after clicking on the topic.

Setting up

Create React App is a template that we will use for this app. What you need to install more is redux, react-redux and react-thunk. Let’s make a few edits in the index.js to create a store and connect the thunks:

One of the key things that a lot of guides miss is saying where Redux can be found in this cycle. Redux is an implementation of the Flux architecture which is a pattern for data flor organization in React applications.

You will need a store to keep an app state in a standard Flux. Action dispatching changes this state. Rerendering occurs according to the state changes like here:

Unidirectional data flow simplifies development. This eliminates the spaghetti effect with the increase of the code base of the app.

Project file structure

Let’s create the root directory /src with the following subcategories:

  • /src/components – weird React components that are not related to Redux;
  • /src/containers – smart React components connected to the Redux store;
  • /src/services – some variants for the API (ex backend);
  • /src/store – all the Redux code is here together with the business logic;

Store directory contains domains that comprise:

  • /src/store/{domain}/reducer.js – reducers that are exported by default and selectors that are exported with the help of named export;
  • /src/store/{domain}/action.js – all the domain action handlers (thunks and simple objects).

State-first approach

There are two stages within our app. The first stage is when a user selects three categories. We can start with the implementation of any kind of Flux cycle but the easiest way is to use state. What state is required for the first stage of the app? We have to save a list of topics received from the server. We should save id of the selected categories with the max number of three. It’s good to save the selection order. For example, if a user chooses more than three topics, we’ll delete the oldest variant from the list.

What will the structure of the state will look like? Take a look:

An url of a page will be a unique id.

Where are we going to keep store this state? Redux has a reducer that stores and updates the state. You will find the reducer in /src/store/topics/reducer.js.

I’ve made a template for creating a reducer. I advise choosing a seamless-immutable library for keeping the immunity of the state.

The first scenario

We can go on developing the app after modeling the state. Let’s create a component that will display a list of topics once they appear. This component will be connected to the reducer which means that the component is smart as it’s using Redux. Let’s create it in /src/containers/TopicsScreen.js

Get the template for the smart component. Our task is to call it inside the root App component. And once everything is ready, it’s time to get a few topics from a Reddit server. Keep in mind that smart components shouldn’t contain any logic besides action dispatching.

Our scenario starts with the use of componentDidMount presentation method. We cannot start the logic right from the presentation. We need to dispatch the action to get the list of topics. We’ll need thunks for implementing this asynchronous action.

We’ll create a new service to receive the current state of the network for a more comfortable work with Reddit API. We’ll use await for this asynchronous method. I like using async/await API  and this is the reason for me not to use promises.

The service returns an array but we have an app storing data in a form of a map. Action body is a great place for turning the array into the map. To save data in the store, we need to call the reducer delegating the object TOPICS_FETHCED.

A few words about the services

We use service to work with the external API such as server-API or Reddit API. The major benefit here is that the code becomes more independent from the API changes. If Reddit makes some updates in the future, they will influence only our services and not the whole application. Keep in mind that services should be stateless.

This rule is not so obvious. Let’s imagine that our API required a password. We could save the state for the login with the help of the service. But we wouldn’t do this because of the development methodology. The whole state of the app should be located in the store. The right solution here is transferring each service with the login data in a form of an argument and saving the login state in the reducer.

Scenario finish line. Reducer and performance

TOPICS_FETCHED object contains a list of topics topicsByUrl and it is transferred into reducer as an argument. Reducer shouldn’t do anything but saving the following data in the state:

 

Take a look at the use of seamless-immutable. We use this library to make the changes even and easy to understand. It’s optional to use such libraries and I prefer using them with the spread operator. We should call the performance rerenderer after the state gets updated. We use mapStateToPros to see the changes in the components of the state that the performance depends on.

We will use ListView to display the list of the topics accepting rowsById and rowsIdArray props. We use mapStateToPros to handle the props inside the TopicsScreen. We get props from the state. Smart components use selectors to address the state.

Selectors are one of the major Redux tools that many developers forget about. A selector is a pure function that takes a global state as an argument and returns it in a transformed view. Selectors are located in the reducer.js.  The reason for this is encapsulating the inner state of the app and hide it from the view.

Think of how we can change the inner structure. If we didn’t have selectors. We would have to make changes to every single component of the view and this is not good. Now we have the following view of the topics/reducer.js:

A few words about container components

ListView is a good example of a container component. It’s not connected to the store and it doesn’t use Redux. Such components are located in /src/components. Weird components get the data from parents through the props and they can save the local state. When do we need to move from the smart component to the container one?

All the presentational logic of the smart components should be transferred to the container ones. You can see some presentational scrips in the ListView release. We should avoid using such a logic in the TopicsScreen container. This lets us reuse ListView.

The next scenario: choosing multiple topics

The first scenario is over. It’s time to move to the next one. A user can choose only three topics from the list. The scenario starts when the user clicks on one of the topics. TopicsScreen stops the process. We should dispatch a new selectTopic action as the container component cannot keep any logic. We’ll use thunks to release this action and we’ll put it in topics/action/js. As you could notice, merely every exported action is a thunk.

It’s essential for thunk to get access to the state. Access to the state is available through the selector. We should update the reducer the way it could handle TOPICS_SELECTED and save new selected topics. Can we use selectTopic as a simple action object and move it into the reducer? This is an acceptable variant, but I prefer keeping the logic in thunks.

We should return the list to our presentation after updating the state. It means we should add selected topics to the mapStateToProps. We use map for the data transfer. The data will go through the selector in any case, so we will conduct the mapping there. We’ll implement the above-mentioned idea and add the background color change when selecting a new category inside a container component ListRow.

A few words about the business logic

A good methodology is about the separation of presentation and business logic. Where can we find the business logic? It’s in src/store/. A bigger part of it is released as thunks in action.js and another part is released inside selectors in reducer.js. Therefore, all the business logic should be inside thunks, selectors and reducers.

A list of posts

We need a navigation when we have more than one screen in the app. One of the most common tools is react-router. I don’t like routing not to make the app too complicated. We’ll add a state variable selectionFinalized that will inform us about the end of the topic selection. When a user picks up three topics, we’ll show a button that will let the user finish the selection and move to a new screen.

We will dispatch an action that will set up selectionFinalized value after a click on a button. It’s similar to the things we’ve done before. We should know for sure when to show the button. Turn App into a component that with mapStateToPros will catch the changes in selectionFinalized.

A screen for the posts

We’re aware of the methodology, so we can speed up the release of the second stage. This stage is about a new posts subcategory. Let’s create a new reducer and a new state for posts to save the modularity of a new app.

The goal of the second stage is displaying a list of posts that you can filter depending on the chosen category. Users may click on the post from the list and see its content. So we get the following:

The first scenario: a list of posts with no filters

We need a smart component for displaying the posts. Let’s name it PostsScreen. We will dispatch a new action called fetchPosts when componentDidMount calls the component. An action will be a thunk that we will create in posts/action.js.

At the end of the thunk, we’ll dispatch a simple POSTS_FETCHED action that will transfer the data into a reducer. We’ll need to update the reducer to store data. The next step will be displaying a list of posts in PostsScreen. We should switch mapStateToProps to the selector that will return the list with ListView.

The next scenario: posts filter

A user is viewing available filters. We get the data using topics state reducer with the help of the existing reducer. We dispatch an action that changes the state with the posts reducer. We save data as postsById and currentFilter. We get a filtered result, so we won’t save it in an app state. Business logic will run in a selector prior to mapping in mapStateToPros. The selector will look like the following:

 

The last scenario: post content

This is the easiest scenario. We have currentPostId variable. We need to update it by dispatching an action when a user clicks on the post. This state variable will be required in PostsScreen to display the post content. It means we need to create a new selector for managing this data in mapStateToProps.

The finish line

What conclusions we can come up with:

  • Application state is an essential basis that structures an app into a database;
  • Smart components should not contain any logic except action dispatching;
  • Smart components should get access to the state only via selectors;
  • The logic of the smart components should be moved to container components;
  • All business logic should be in thunks, selectors, and reducers;
  • Services shouldn’t depend on the state.
Yaroslav Golovach
y