DEV Community

Cover image for How to Start ReactJS Development Fast. Part 2: Best Practices
Codica
Codica

Posted on • Edited on

How to Start ReactJS Development Fast. Part 2: Best Practices

Today, we are going to provide you with the second part of the article How to Start ReactJS Development Fast: 3 Solid Tools and Best Practices. Here we want to provide you with our best practices on React-Redux project structure to give a better understanding of the application architecture.

Best practices on React-Redux project structure

In this section, we want to share our base recommendations on how to structure your React-Redux project files and code so that your application stays maintainable as it scales, based on our experience. The guideline is based on improving the create-react-app setup.

Initially, we decided to separate React and Redux into different folders. Thus, it simplifies the process of applying any changes or adding a new feature. Redux-specific code (reducers, actions, action types) is split by feature-first pattern Re-Ducks. Here is an example of a project structure we use in practice:

src/
├── state => redux specific code (Re-Ducks)
|   ├── ducks/
|   |   ├── duck/
|   |   |   ├── types.js
|   |   |   ├── actions.js
|   |   |   ├── selectors.js
|   |   |   ├── reducers.js
|   |   |   ├── tests.js
|   |   |   ├── index.js
|   utilities/ => global constants and helper functions
|   views/
|    ├── routes/       => base router
|    ├── components/   => feature-first components
|    ├── pages/        => layouts, related to routes
|    ├── styled/       => StyledComponents
|    └── UI/           => reusable components
Enter fullscreen mode Exit fullscreen mode

We prefer to create the React components first and then the corresponding Redux code. It allows us to have a general understanding of the data requirements.

The /ducks directory has a fixed pattern. We use a modified version of the ducks pattern to organize our Redux code:

ducks/
├── duck/
|   ├── actions.js
|   ├── reducers.js
|   ├── types.js
|   ├── utils.js
|   ├── selectors.js
|   └── index.js
└── index.js
Enter fullscreen mode Exit fullscreen mode

Now, let’s discuss each /duck folder file to understand why is it important and what it stands for.

Project structure files

types.js
This file contains string literals for our action types. It provides an easy reference to the actions available. These strings are exported as an object literal which can then be imported into your reducers and action creators instead of hard-coding them. Although maintaining a separate file that contains the action types is optional, it is highly recommended in organizing the structure of your project files.

// types.js
export const SOME_YOUR_TYPE = "SOME_YOUR_TYPE";
Enter fullscreen mode Exit fullscreen mode

actions.js
In this file, we define all the actions. Actually, some developers tend to separate async actions and action creators, but we don’t think it is pretty crucial.

// actions.js
import types from './types.js';

// action creator
const someAction = payload => ({
  type: types.SOME_YOUR_TYPE,
  payload
});
Enter fullscreen mode Exit fullscreen mode

Actually, we use redux middlewares such as redux-thunk or redux-promise-middleware for dispatching async actions.

reducer.js
The reducer is required to update the state. We create a single reducer for each action using createReducer. We use this command to create reducers, not the basic switch-case template. The matter is it's very useful, for example, if you need to scope out part of a reducer to use variables with the same name in several case statements.

// reducer.js
const someReducer = createReducer(initialState)({
  [types.YOUR_ACTION_TYPE]: (state, action) => {
    return {
      ...state,
      some_prop: action.payload
    };
  },

  [types.SOME_ANOTHER_TYPE]: (state, { payload: { data } }) => ({
    ...state,
    data,
    loading: false
  }),

  [types.MAY_BE_YOU_WANT_RESET]: (state, action) => ({
    ...initialState
  })
});
Enter fullscreen mode Exit fullscreen mode

selectors.js
In Redux, a selector is a piece of logic that receives a certain piece of state from the store. Additionally, a selector can compute data from a given state, allowing the store to hold only basic raw data. Selectors are usually used as a part of the binding between the store and the container components.

We use the Reselect library to create selectors. This library is not the only way or the requirement to create selectors. However, it gives several advantages in terms of developer experience and performance:

  • Selectors created through a createSelector function are memoized. It means that the function remembers the arguments passed-in the last time it was invoked. Thus, it doesn’t recalculate the result if the arguments are the same.
  • Selectors can be composed/chained together easily. This way, each selector stays small and focused on one task.

Here is a simple filteredTodos selector example to demonstrate how it works:

// selector.js

import { createSelector } from 'reselect';

const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;

export const filteredTodos = createSelector(
  [todoSelector, searchTermSelector],
  (todos, searchTerm) => {
    return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
  }
);
Enter fullscreen mode Exit fullscreen mode

With the help of this library, we can use the filteredTodos selectors to get all the todos if there’s no searchTerm set in the state, or a filtered list otherwise.

Also, we can get all the todos in a flat shape in conjunction with normalized data:

import { denormalize } from 'normalizer';

import { todo } from '../../schemas';

const getById = state => state.todo.byId;

const getAllIds = state => state.todo.all;

export const makeAllTodos = () =>
 createSelector(
   [getAllIds, getById],
   (all, todos) =>
     denormalize(all, [todo], { todos}),
 );
Enter fullscreen mode Exit fullscreen mode

index.js
Here, we re-export all our actions, selectors and our reducer as a default export.

// index.js
export * from './actions';
export * from './selectors';

export { default } from './reducer';
Enter fullscreen mode Exit fullscreen mode

Finally, our duck folder is ready!

This is the way we organize our React app structure to make the application maintainable when it scales.

Conclusion

At Codica, we have created our Best Practices on React-Redux project development that can help you understand your application architecture and build a well-structured code. Our experts believe these recommendations will help you properly organize your project structure to make it easy-to-maintain and easy-to-read.

Stay tuned and check our full article: How to Start ReactJS Development Fast: 3 Solid Tools and Best Practices.

Top comments (1)

Collapse
 
kimsean profile image
thedevkim

I learned a lot from this post. Thank you. I'll be following this structure on my future projects.