Fast track React

In this post, I will show you the fast way to fire up an React app. Of course, we will use CRA, several awesome libraries such as redux-toolkit, react-redux, axios and so.

1. Start up

Let’s find a good directory and fire up the following command:

# Terminal
npx create-react-app <app-name>

2. Dependencies

You will need checking .gitignore and add in anything that you don’t want to include. Run either one of the commands below to install the necessary dependencies:

npm install react-redux react-router-dom redux-persist @reduxjs/toolkit axios

# OR

yarn add react-redux react-router-dom redux-persist @reduxjs/toolkit axios

Open package.json and verify if following libraries exist under dependencies field (versions might not be the same):

...
"@reduxjs/toolkit": "^1.6.1",
"axios": "^0.21.4",
"react-redux": "^7.2.5",
"react-router-dom": "^5.3.0",
"redux-persist": "^6.0.0",
...

3. Project Structure

I’m lazy, so I use the same approach for every app to save time and energy. Within the src directory, create following directories:

cd src && mkdir components store && mkdir store/middleware

Folder components is for React components in various levels: layout, sidebar, main content, … While store is for Redux stuff.

4. Redux toolkit

Redux toolkit eases the pain of repetitive coding style required by Redux (in Javascript). You will be surprised that we don’t have any reducer/action file/folder, instead, we only have things called Slice. It’s all you need.

You would need to create a store.js file in store folder to gather all the slices available in your app.

import { combineReducers, configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import storage from 'redux-persist/lib/storage';
import { persistStore, persistReducer } from 'redux-persist';
import api from './middleware/api';
import uiSlice from './uiSlice';

const reducers = combineReducers({
    auth: authSlice,
    ui: uiSlice
});

const persistConfig = {
    key: 'root',
    storage
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = configureStore({
    reducer: persistedReducer,
    middleware: [
        ...getDefaultMiddleware(),
        api
    ]
});

const persistor = persistStore(store);

export { store, persistor };

5. Network middleware

In order to have a solid API call workflow, we have to setup a middleware file which I called api.js in folder store/middleware.

import * as actions from "../api";
import { showMessage } from "../uiSlice";
import axios from "axios";

const defaultHeaders = {
    'Some-Header': process.env.REACT_APP_APP_TOKEN
};

const api = ({ dispatch }) => (next) => async (action) => {
    if (action.type !== actions.apiCallBegan.type) {
        return next(action);
    }

    const { url, method, data, headers, onStart, onSuccess, onError } = action.payload;

    if (onStart) {
        dispatch({ type: onStart });
    }

    next(action);

    try {
        const response = await axios.request({
            baseURL: `${process.env.REACT_APP_API_END_POINT}`,
            url,
            method,
            data,
            headers: { ...defaultHeaders, ...headers }
        });

        dispatch(actions.apiCallSuccess(response.data));

        if (onSuccess) {
            dispatch({ type: onSuccess, payload: response.data });
        }
    } catch (error) {
        dispatch(actions.apiCallFailed({ error: error.message }));
        dispatch(showMessage({ message: error?.response?.data?.error_message }));

        if (onError) {
            dispatch({ type: onError, payload: { error: error.message } });
        }
    }
};

export default api;

6. Environment variables

In the root folder, you can create .env and .env.development to set environment variables. Remember that every ENV that is used by CRA must be prefixed with REACT_APP_ and used with process.env.REACT_APP_.