A simple React and Redux Architecture

There are a lot of different ways to have a system built around React and Redux. The freedom is mainly because both frameworks are not opinionated on how developers leverage them. In this article, I’ll present a simple architecture that uses React and Redux. The idea is to not complicate the lifecycle by avoiding an excess of third-party libraries and instead focus on some core ones.

UI Components

The premise of having a simple code starts with having simple UI elements. Simplicity follows the React philosophy of having small and simple components. It makes code reading easier for developers who need to change it, makes writing unit tests faster, as well as reduces the side-effects that occur when modifying a piece of code. Most of the components won’t have a state at all. By most, I mean more than 95%.

Most of the stateless components will be presentation components. These do no connect to Redux – they get their data from their properties and display it. When needing to take action, they dispatch using the properties as well. There are multiple reasons for this, the main reason being reusability. You can reuse these components regardless of the lifecycle. They are not tied to Redux or any other framework you may migrate to in the future. The second reason is simplicity. It is simpler not to be dependent on the store for testing.

The second type of component is the container component. These components are the ones that are listening to the Redux’s store. Page component and heavy control like grids may be connected to the store. However, all controls within the pages or grids shouldn’t.

React-Redux is the main library used to glue React components to Redux, and it is perfect to use for container components, but having too many of such components breaks the React hierarchical flow. That’s right; if every component hooks to the store, any change to the store will notify every component instead of just a few select ones that can then distribute the changes down. The flow is more predictable if it goes top to bottom than every component getting notified. That being said, a balance is required as well. Otherwise, too many components will have a huge properties list or have a very high-level property which makes them access too many values, thus confusing developers what they should access.

Loading Page

Everything starts when a page is loaded. React-Router is a key third-party library at this point. Based on the URL that is active, it loads the right component. A pattern we see frequently is to have the ComponentDidMount method call Ajax to load data and then dispatch an action to have the data stored in Redux’s store. Saving a value in Redux fires an event that the data in the store has changed which then notifies all the components that are listening to the store. The chain of reaction is fine, but I’d rather not have a component start the process of the data fetching. The first reason is that a component should be very dumb. The mounting of a component which only performs display and dispatches operations does not necessarily mean that we want to fetch the data. A change in the route, on the other hand, triggers the need for new data. Therefore, in this architecture, the loading of data is driven by a custom middleware that looks at the route change and executes the proper calls that will fill up the Reducer.

Business Logic

Business logics are the core of your system, especially when it is more than just a CRUD application where we take data from the server and throw it on the screen. In this architecture, all domains have their custom middleware. A logic may request data from an API, manipulate it, ask for more data and when ready, store the data in Redux Reducer. The ideal position for business logic is in the middleware since they have not only had access to the actual store (getState) but also to the API call. It can be asynchronous with the help of Redux-Thunk and fit well in architecture with pre and post middleware that we will see soon.

Normalization and Store

Data comes in many formats, and sometimes some entities are part of many data branches. Normalizing the data means avoiding any duplication of data inside the store even if the data is used in many branches. Normalizing should always be done before sending the data to the Store. The reason for having a middleware to normalize the data and then send it to Reducers makes sense. Avoiding duplication is key to a stable system where the store is a source of truth.

Pre and Post Middleware

Every time an action is dispatched, the lifecycle flow of information goes from React component to middlewares to reducers to the store which then notifies the React component. The pre and post middleware are middlewares located before and after the domain middleware.

Pre-Middlewares in this architecture are middlewares for Thunk, operational, routing and authentication. Post-Middlewares are for normalization and telemetry. Let’s take a look at some of them to have a better idea. Post-Middleware prepares the data for business logic ones where the domain logic will be executed.

The Redux-Thunk library is the one that transforms actions to be asynchronous. This is quite useful as it enables us to dispatch multiple actions from a single middleware. For example, when you are loading data, you might dispatch an “is loading” action, when the data received is “data loaded” action and finally “save data in reducer” action.

The operational middleware is not required here, but I found it useful to associate a unique ID to a lifecycle flow. This data is used to tag logs into a specific lifecycle.

The routing middleware is divided into two middlewares, which are used to pass the routing information into the system. The first is the official one from React-Redux which handles the history/location data. The second one is more interesting since it’s the one in which we will manually analyze the route and figure out if we want to load data when a route has changed. This middleware can dispatch many actions. For instance, it can set the active entity by looking at the URL, dispatch a “loading” action for a particular set of data, and deconstruct the URL to know if a whole page is changing or if the URL is for a deep link which may require different actions to be dispatched. In the end, this middleware’s role is to handle the application logic regarding a route change.

The authentication middleware is the one that can look if the authentication token/cookie is still valid; if not, to request for a refreshed token before continuing the dispatch of action. I often queue every action until the authentication is validated. It can also be used as an authorization gateway if a particular credential fails to redirect. Being early in the pipeline, you can block it easily if needed.

Jumping to post-middleware, the most obvious one is the middleware that normalizes the data. This middleware listens to all activities that have a role in saving data to the reducer. It will normalize the data and send multiple dispatches to reducers. I recommend sending one dispatch per entity type since you can have a lot of reducers that are doing different manipulation per type. Since you should have one reducer per entity, it makes sense to have a more granular dispatching process. In the end, reducers will benefit by having a simpler immutability logic that is targeted to the reducer itself.

The last post-middleware can be a telemetry one where you may want to capture some actions for telemetry purposes. See this middleware as a logging middleware that avoids cluttering domain middleware.

Conclusion

This architecture is not fancy, making it shine by being easy to read and to change. If you have a new page to build, you only need to create your dummy React component, a middleware for the page if it’s not from an existing domain and adjust the normalization and reducer (if new entities are required).

TypeScript Redux Action from User Defined Guard to Discriminated Union

Using TypeScript with Redux bring many questions about what really is passing around. Enforcing a strongly typed model and keeping the flexibility while using Redux is a challenge that doesn’t have a single solution. In this article, we will see the first approach using user-defined guard and see how to transform this one to use a discriminated union.

Redux is all about sending a message from the React component to the Redux store. In the middle, you can manipulate the message sent to middleware. The message sent is called “action”. The action contains the payload that can be analyzed in the Redux’s flow and transformed to finally be persisted in the single store. A common pattern is to send the payload by using a function that creates that message which is then passed to Redux’s dispatch which will send the message in its normal flow. For example, in your React component, connected with React-Redux, you can dispatch a function that returns your payload. The payload should contain a unique identifier and the payload at the minimum. The unique identifier allows middlewares and reducers to know if they should catch the message or just let it goes. The payload is to be manipulated by the middlewares or stored in the store by the reducers. The first approach is, with a user-defined guard, is to create a reusable generic action which will be the envelope for your message.

const MY_OBJECT_UNIQUE_ACTION_ID_HERE = "MY_OBJECT_UNIQUE_ACTION_ID_HERE";

// Reusable generic action
export interface Actions<T> extends Action {
    readonly type: string;
    payload: T;
}

// Example of a function that creates an action which is used in the Redux's dispatch
export function createActionToSaveMyObject(data:MyObject): Actions<MyObject> {
    return {
        type: MY_OBJECT_UNIQUE_ACTION_ID_HERE,
        payload: data
    };
}

// The dispatch done through the React-Redux's API
dispatch(createActionToSaveMyObject({id:1, name:"test"}));

Inside the middleware, to consume the action, you need to do two things. Like any Redux code, you need to check the type and compare it against the unique constant for the action. However, since we are using a generic action, it needs to have all possible type as a union inside the generic. This cause the action to have a payload not very strongly typed. It is but could be any of the type specified in the union of the generic type of Actions. To narrow down, we need to use a user-defined guard. We could have a user-defined guard function for each type, or we could cheat and have one type that just cast it to the desired type.

// Middleware example 
export interface ExtendedMiddleware<StateType> extends Middleware {
    <S extends StateType>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

export const yourMiddleware: ExtendedMiddleware<AppReduxState> = <S extends AppReduxState>(api: MiddlewareAPI<S>) =>
    (next: Dispatch<S>) =>
        <A extends Actions<MyObject
            | MyObject2
            | MyObject3
            >>(action: A): A => {
            if (action.type === MY_OBJECT_UNIQUE_ACTION_ID_HERE && isGenericAction<MyObject>(action)) {
                // Logic that can access action.payload that is strongly typed with MyObject
            } 
        });

// Reducer is using the same logic

export function youReducer(
    state: AppState = initialState,
    action: Actions<MyObject
            | MyObject2
            | MyObject3
            >>
): AppState {
    if (action.type === MY_OBJECT_UNIQUE_ACTION_ID_HERE && isGenericAction<MyObject>(action)) {
                // Logic that can access action.payload that is strongly typed with MyObject
    } 
}

// The cheat:
export function isGenericAction<T>(obj: any): obj is Actions<T> {
    const castedObject = (obj as Actions<T>);
    return castedObject.payload !== undefined && castedObject.type !== undefined;
}

As you can see, the problem with this approach is that is that we must use everywhere the function that will return the narrowed type. It’s convoluting and also bring some issues. For instance, if you use the function and pass the generic type to something else that really what is the object, the cast will allow it and indicate to TypeScript the wrong type. isGenericAction(objectType2) will indicate to TypeScript that the object passed is of type 1 because the condition to evaluate is weak and always true for any time that extends the generic actions interface created. This approach has the advantage to be succinct anytime you need to create a new action which is to create an action creator function, an unique constant and a check when needed.

An alternative is to use a discriminated union. This approach has more boilerplate but will eliminate the need to use a function to coerce a narrowed type. TypeScript will take care of doing it. The main advantage of using a discriminated union is that it is strongly typed. The disadvantage is that we are losing some flexibility since we cannot have anymore the member “type” to be a string. It needs to use a discriminated field which will be of the type of a unique string and not a string. For instance, the type will be the name of the constant as well as its values instead of being of type string with the value of the constant.

const MY_OBJECT_UNIQUE_ACTION_ID_HERE = "MY_OBJECT_UNIQUE_ACTION_ID_HERE";

// Reusable generic action
export interface Actions<T> extends Action {
    payload: T;
}

// Every action must have an interface with its discriminator
export interface ISaveMyObject extends Actions<MyObject> {
    type: typeof MY_OBJECT_UNIQUE_ACTION_ID_HERE;
}

// Example of a function that creates an action which is used in the Redux's dispatch
export function createActionToSaveMyObject(data:MyObject): ISaveMyObject {
    return {
        type: MY_OBJECT_UNIQUE_ACTION_ID_HERE,
        payload: data
    };
}

// The dispatch done through the React-Redux's API
dispatch(createActionToSaveMyObject({id:1, name:"test"}));

So far, little changed. However, we see that we need to create an interface and set the type to a unique name. A good practice is to use the constant and with typeOf extract the string of it and assign it as a type. In the action creator function, you can assign the value of the constant. This might not look like a big drawback from user-defined guard, but it can be involving on big system with hundreds and hundreds of action. Nevertheness, middlewares and reducers are simplified. Since every action is having a unique interface with a unique discriminator under the common member “type”, it’s possible to check against the discriminator and TypeScript will know which interface it is associated with. Since every interface extends the actions that contain a strongly typed payload, the type is accessible directly.

export interface ExtendedMiddleware<StateType> extends Middleware {
    <S extends StateType>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

type AcceptedAction =
    MyObject
    | MyObject2
    | MyObject3
    ;

export const yourMiddleware: ExtendedMiddleware<AppReduxState> = <S extends AppReduxState>(api: MiddlewareAPI<S>) =>
    (next: Dispatch<S>) =>
        <A>(action: A): A => {
            const actionTyped = action as any as AcceptedAction;
            if (actionTyped.type === MY_OBJECT_UNIQUE_ACTION_ID_HERE) {
                // Logic that can access actionTyped.payload that is strongly typed with MyObject
            } 
        });

// Reducer is very similar
export function yourReducer(
    state: AppState = initialState,
    action: Actions<A>
): AppState {
    const actionTyped = action as any as AcceptedAction;
    if (action.type === MY_OBJECT_UNIQUE_ACTION_ID_HERE) {
        // Logic that can access actionTyped.payload that is strongly typed with MyObject
    } 
}

There is one dirty trick that you can see the action being cast to “any” and cast to the list of accepted type. Ideally, the action would be of type of the union. However, Redux definition files require having a type A. Extending the type create confusion to the compiler which doesn’t know which of the type and thus have the payload of all the unionized type.

In this article, we saw two function ways to handle action from React to Redux’s state. The first one is using a user-defined guard which was quicker to write but loosely strongly typed. The second approach is more convoluted but is strongly typed and protect you from having a false sense of security that the code is typed with a specific type which may be deceptive. Neither of the design are peculiar but the latter is by experience worth the additional typing.

A common fallacy around A/B testing

Doing A/B testing has been popular for a while and if you wander job descriptions and talent-recruiter emails you see that it’s been used as an element to lure people. Who wouldn’t want to work for a company that is not dictating behaviors of a system and in which the decisions are rationally taken by numbers?

The issue I’ve been witnessed during the last few years is that sometimes, not always, conclusions are drawn because this is the best the data could achieve. However, the statement declaring the winning scenario miss the asterisk mentioning that the final decision was taken without relying on the state of the art rational of the metric collected but by some assumption pulled from some numbers. The reasons of playing the number in one side more than another can be wide, but most of the time having a bad A/B test or misreading the results work in favor of someone who wants to pass an idea.

Let’s take one scenario of many. Imagine you have a system with many costly features. By costly, I mean that they are hard to maintain or cost money because of resources. Two people argue. One desire to cut the feature while the other one rather fixes all the issues. Before taking a decision, the one who would like to cut the feature suggest to do A/B testing with a control and base group which would determine if users are more susceptible to leave the product if these features got removed. This looks rational, both people agree. They remove one feature. After a while, the data come back and the result shows that users count was steady and very similar to the base group. The conclusion is that removing the feature was not significant. Engineers change the code by removing the tested feature on the product. Then another feature is being tested on a sample of users — the same result. After iterating for a while, all expensive features have been taken off. The last A/B showed that more people left than the previous one, not by a huge amount but still enough to say to keep the feature. Still, a good gain since in the group barely all the features got cut.

There are two problems with this scenario. The main one is the fallacy that there was enough data collected to take a decision. Removing content or altering existing content goes beyond the data that can be collected by the product where the change is made. If you remove a feature and the user is not satisfied, the result doesn’t mean that he will leave the product. He might keep continuing because of other features. However, he might as well join another service to complement the gap. The unsatisfaction is hard to collect. For example, you have a music service, you remove some editing feature, maybe the user keep using the service because you have a great catalog of music, but also will use another service for editing music. The problem here is that if the editing music service gets a good catalog that the user will eventually leave which by the time won’t be shown in the result of the old A/B testing.

The second problem in the scenario described is that the feature was A/B tested individually. Do not get me wrong, it’s often safer to do small move than a big one. However, in our example, when the last feature was taken out and that the data shows user behaving differently, there is something missing in the data. The data doesn’t show that the user got irritated by all these features removed across the last few months. The user is now at his/her limit and leave — not particularly because of the last feature, but because of the sum of all of them. The conclusion drawn will be that the last feature should be back, but the reality is that it won’t change anything.

There is many A/B testing that is conducted the right way, which totally makes sense to base a future decision on the collected data. For example, you are testing two sign-up buttons on your page. One is getting more people to join your product in the very similar environment than the second test.

Satisfaction is always hard to get into the equation when performing A/B testing. This is why it is wise to be careful when using the term A/B testing. It might sound the right thing to do for all decisions, but there is an area where the test might end up being a fallacy that just look great since it back up an hypothesis that we cherish more than the reality.

Dissecting the store enhancer of Redux

In previous articles, we dissected the compose function of Redux and the createStore function. The createStore uses the compose function to have many store enhancer. In this article, we will see an example of a store enhancer by dissecting one of the most used store enhancer: applyMiddleware.

It might be a surprise to some that Redux doesn’t have baken in Redux the notion of middleware. While it is part of the package, it uses the core notion of store enhancer to bring the middleware concept in life. To have middlewares being executed between a dispatch call on the store and reducers to alter the store’s state, an alteration of how dispatch work must be done. As we learned, store enhancer allows to modify the store object that contains the dispatch and this is what applyMiddleware does.

Before discussing more, let’s see the Redux applyMiddleware function.

export default function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

The function takes a list of middlewares. This is custom to applyMiddleware which let have a list of composed function that will be called. If you are building your own store enhancer, you will need to pass whatever you anticipate being needed to modify the store behavior. What is important is the second line which returns a function that has the createStore function that returns a function with many arguments which return the store. In that case, the last return of the function returns a copy of the store (spread operator) with the dispatch function redefined.

Let’s rewind a little bit. The return function that returns a function is actually calling createStore with the args list of argument. At that point, the function could return the result and nothing would be changed. Obviously, something will be done. First, the method takes a reference to the actual dispatch function. Then, it creates an object that will be passed down to all middleware. This object has the actual store’s state and a dispatch function that call the original dispatch function. The whole middleware logic is the next line which will invoke all the middleware passed by parameter one at a time. The middleware passed by parameter are functions are well, which take the “middlewareAPI” object. At that point, we only have middleware function that is having access to the getState and dispatch functions. The next line will chain them by passing the store.dispatch function. Every middleware has access to the next one by calling “next”. Often you will see that people invoke “next” with a new action. When doing so, they call the next middleware with a new action which is totally valid but won’t go through the whole chain, just forward. The store.dispatch passed is there to trigger at the end of the chain the reducer.

The return of each middleware is the result of the previous middleware. The return will be used by the following middleware a way to traverse the chain of middleware by calling next on the action.

Here is a middleware example that console.log before and after a dispatch is invoked. The format is disquieting at first with the equal sign followed by the three arrow functions. You do not have to use this sugar syntax, but it reduces quite a lot and you will often see this written format. What it does is to store the middleware in the “logger” variable. The variable is a function that has for parameter the store. The store contains the “middlewareAPI”, hence getState and dispatch. As we saw, the composition calls the chain of middleware with “store.dispatch” and return the next middleware which is referred by the named “next”. Finally, the action being dispatched is available.

const logger = store => next => action => {
  console.log("Before", action)
  const result = next(action)
  console.log("After", store.getState())
  return result
}

For completion, let’s analyze a very popular middleware called Thunk which brings the possibility to have several dispatches. Because, so far, every middleware receives a getState and dispatch from “middlewareApi” and return the next action has an object which doesn’t give the time to perform any asynchronous logic. The reason is that the function applyMiddleware is composing all middleware, hence you have a function with “next” that lets you chain them. Finally, the function with the parameter “action” which is the what is being dispatched.

The Thunk middleware looks up to see if the action dispatched is a function or an object. Normally, it would be an object with the required “type” member defined (see the createStore that throw an exception otherwise). The Thunk middleware invokes the action by passing the “dispatch”, “getState” and “extraArgument” and it returns the result of the action. The “dispatch” function passed down lets you invoke several time actions if needed while the getState function lets you peek at the current state to perform some business logic in your middleware. For example, you could see if some data is missing in the store to invoke some APIs.

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

With Thunk in the middleware composition and dispatching a function, what happens is that other middleware will ignore the function and the thunk will catch that it’s an action and then invoke the action.

export function asyncMethodThatFetchData(inputData) {
  return function (dispatch, getState) {
    dispatch(actionLoadingData());

    return fetch(`https://www.api.com/${inputData.id}`)
      .then(json =>
        dispatch(actionDataLoaded(inputData, json));
      )
  }
}
store.dispatch(asyncMethodThatFetchData("123"));

This simple function is dispatched by the store with the argument “123”. It will be intercepted by the Thunk which will execute the return of the function which is the function that takes the dispatch and getState argument. Within the function, there is a first dispatch that is called which could be used to start a loading animation and a second one once the data has been fetched.