Software developer

Developer of webapps and more since 2016

03 Apr 2020

371
Notice on Redux

Tags

Introduction

I wrote a post previously about Elm and it’s architecture. Here we will focus on react and redux, and I’ll try to be objective.

React has a thing called local component state. It is convenient but sometimes you are tempted to add a lot of stuff in it, and this stuff is not accessible from other components. If you need to trigger an animation on other components, or centralized data accessible to all the components, you’ll need redux.

If you don't know what is Redux, Redux is inspired from the Elm architecture. It is composed of reducers, (functions that takes the redux state and an action in argument and returns a new state ) and actions (objects that have a key to match a reducers case, and a payload to update the redux state).

Here is an example of all the code required to do an http call to a server and storing the result in the redux store using react, redux and Redux Saga with a synchronized loading state for the request.

// constants.ts

export const ME_FROM_TOKEN_REQUEST = 'ME_FROM_TOKEN_REQUEST';
export type ME_FROM_TOKEN_REQUEST = typeof ME_FROM_TOKEN_REQUEST;

export const ME_FROM_TOKEN_SUCCESS = 'ME_FROM_TOKEN_SUCCESS';
export type ME_FROM_TOKEN_SUCCESS = typeof ME_FROM_TOKEN_SUCCESS;

export const ME_FROM_TOKEN_FAILURE = 'ME_FROM_TOKEN_FAILURE';
export type ME_FROM_TOKEN_FAILURE = typeof ME_FROM_TOKEN_FAILURE;



// authActions.ts

export interface MeFromTokenRequest {
    type: constants.ME_FROM_TOKEN_REQUEST;
}

export interface MeFromTokenSuccess {
    type: constants.ME_FROM_TOKEN_SUCCESS;
    payload: { jwtToken: string; user: User; };
}

export interface MeFromTokenFailure { 
    type: constants.ME_FROM_TOKEN_FAILURE; 
    payload: {
         message: string;
     };
}

export const meFromTokenRequest = (): MeFromTokenRequest => ({
    type: constants.ME_FROM_TOKEN_REQUEST
});

export const meFromTokenSuccess = (payload: MeFromTokenSuccess['payload']): MeFromTokenSuccess => ({
    type: constants.ME_FROM_TOKEN_SUCCESS,
    payload
});

export const meFromTokenFailure = (payload: MeFromTokenFailure['payload']): MeFromTokenFailure => ({
    type: constants.ME_FROM_TOKEN_FAILURE,
    payload
});


// authReducer.ts
export default function authReducer(state: User | null = null, action: AuthAction) {
    switch (action.type) {
        case constants.ME_FROM_TOKEN_FAILURE:
            return null;
        case constants.ME_FROM_TOKEN_SUCCESS:
            return action.payload.user;
        default:
            return state;
    }
}

// authSaga.ts (we'll talk to that later)

function* meFromTokenSaga(action: authActions.MeFromTokenRequest) {
    const jwtToken = localStorage.getItem('jwtToken');
    if (!jwtToken || jwtToken === '') {
        return yield put(authActions.meFromTokenFailure({ message: 'NO_TOKEN' }));
    }
    try {
        const res = yield AuthApi.meFromToken(jwtToken);
        const user = new User(res.data.user);
        yield put(authActions.meFromTokenSuccess({ jwtToken: res.data.jwtToken, user }));

    } catch (e) {
        yield put(
           authActions.meFromTokenFailure(
                e.response 
                   ? e.response.data
                   : 'NO_RESPONSE'
             )
         );
    }
}

//component.ts

import React from 'react';
import { connect } from 'react-redux';


export default connect(
        (state: StoreState) => ({
            authedUser: state.authedUser
        }),
        (dispatch: Dispatch) => ({ dispatch })
    )(
            class Login extends React.Component {
                 // Use the props, dispatch the action, receive the logged in user
             }

Associated with React-redux, it triggers the render function of the components via the props when the state is updated.

I recently learned Elm, and what amazed me compared to redux is it’s architecture simplicity (especially the update function, which acts like a reducer in redux).

Redux is great, but I should warn about its heavyness and the lot of boilerplate code you need to write :

  • You may have more than one reducer if you need to manage a lot of data.
  • The actions syntax is also heavy compared to Elm, you need a key to match the reducers case, this key being a constant, you need to create a constant for it. In the case of using typescript, you need to create a type for that constant.
  • To get the components updated you need to connect the component to the store via a connect function provided by react-redux.
  • If you wanted to manage asynchronous actions like api calls, you’ll need an extra library like redux saga or redux-thunk.

When you have all these things gathered, you can start programming your job work. You need to know that it is a lot of work to get started.

My word in conclusion

React and Redux are great, furthermore using Typescript but to get started it is a lot of configuration and also while coding it is a lot of work get the job done.

update of 2020-11-21 :

Redux now ships a new way to manage reducers and actions. Redux-toolkit. Personaly, I don't like this extra abstract layer.