Recently, we introduced a feature flag management system into our project. And since feature flags are among others used to display or hide features in the frontend, we had to find a good and uniform way to deal with them in our React components.
Write a hook
We currently started to write function components and use hooks inside them. To get more comfortable using React hooks, it was time we write one ourselves.
The hook itself depends on a the useSelector hook from react-redux, because we store the feature flags in a redux state. Of course you can do that differently if you don’t use redux. The idea behind writing this hook is simply to access feature flags always in the same way, and therefore adjustments on the functionality only have to be made at one dedicated place in the code.
Let’s say you’re currently developing a page that will display the customer’s documents. All you have to do in your component is call the useFeature hook with the flag your feature is depending on. We decided to store the feature flags in an enum. However you can also consider string literal types or – if you don’t use static typings – plain old strings.
Invoking the useFeature hook feels quite natural. If you add/rename/delete a feature flag, TypeScript will support by providing suggestions or highlighting type errors. You will have a single source of truth for your features. And also features with variants would be possible with a hook. Your hook could return a tuple with the first element representing if the feature is active and the second element containing the attributes of the variant.
When we started our new product (Relay, a platform for service providers), we first decided to use TypeScript for the server-side code. Since this went out quite well for us, we also introduced TypeScript for the frontend very early. Now our user interface’s code is entirely written in TypeScript.
Somehow we were never completely comfortable with TypeScript in our frontend. It didn’t feel very type-safe although every single file was written in TypeScript. That’s because the core of the application’s data was missing types when it came to reducers. A classic redux reducer is a function that has two arguments, the current state and an action that was dispatched and can be handled now. But the action’s payload’s type was of type any, a type we should avoid when we want to use TypeScript correctly.
So we looked for a library to boost our redux code and finally have type-safe reducers. That’s where we learned about typesafe-actions, tried it out and added some custom functions that suit our needs.
And action …
In this section we will have a look how our code base changed using that neat library.
Let’s have a look how we previously would have implemented the redux part of a signup form.
There is an interface SignupFormState declared for the redux state of our signup form. It is used in the angle brackets of redux’s combineReducers.
The argument state in each reducer is now correctly inferred from SignupFormState. Unfortunately, each second argument action is of type any. Of course we could set the type of the possible action(s) for each reducer individually, but this would require us to declare and export the interface of the action that would result from each action creator. This approach didn’t look very appealing to us, because it causes a lot of boilerplate code in order to have a nice TypeScript DX. Luckily, typesafe-actions is out there and provides a different way of writing actions and reducers.
Now this is much more concise than before, right? This is because there is no need to additionally define constants for the types and export them for the reducers. Let’s see how the reducers deal with dispatched actions when we do not reuse the type constants.
We still use combineReducers they way we used it before, but the code in the function call clearly changed. Reducers are no longer done by switch-case functions, instead createReducer does this job now. It takes one generic argument which will be used to determine the return type of the reducer and one parameter argument which will serve as the reducer’s initial state. Now, to take care of dispatched actions you can chain handleAction calls on the reducer. The first argument is an action creator and the second one is a reducer-like callback function that infers its parameter types from the action creator.
We didn’t import the createReducer function from typesafe-actions but from typesafe-thunk-actions. Both of them export such a function with very similar behaviors. The one from typesafe-actions needs a type definition that is a union of all possible actions (you can read about that in their documentation) for each reducer (or you have a root action type) and then add a handler for that action. But we wanted to reduce boilerplate code to have a nice DX.
That’s why we started a project called typesafe-thunk-actions that comes with its won createReducer function. A specification for the action types is not needed, because handleAction can infer the action’s payload. But there is a drawback in this implementation. It can only infer standard actions, i.e. actions with attributes type, payload? and meta?. If that doesn’t match your needs, you should consider going with the createReducer function from typesafe-actions.
But the module also contains a function called createAsyncThunkAction that deals with async action creators in a standardized way. Imagine we want to send our signup form to the backend and deal with the response in redux.
submitSignupForm is itself a thunk action and therefore can be directly dispatched. Also it has properties request, successand failure which can be passed to the handleAction function of a reducer. The generic parameters take one argument which mirrors the async function’s return type. You don’t have to specify it when it can be inferred from the async function. The async function takes up to 3 arguments. The first one is the one you will call the resulting action creator later. In this case we specified void. That means we can dispatch the function without an argument:
The dispatch and getState arguments are the same you might know from redux-thunk. In the example we use getState to pass the form data from our redux state to the server.
Not only the reducers are really typesafe now, we also managed to write less code at the same time. typesafe-actions and typesafe-thunk-actions come in really handy with their create*Action and createReducer functions. They enhance your DX by bringing type safety to redux apps. For detailed API descriptions please visit the GitHub repositories of typesafe-actions and typesafe-thunk-actions.