Switch statements replace long if-else chains. They make it easy to return different results depending on the value of some variable. Also, they are commonly used in React if you want to render different elements depending on the current state of your application. And that’s perfectly fine.
Problems arise as soon as you start overusing them. When you create functions that use the same switch statement over and over again. This is the point where tools for static code analysis start complaining about increasing cognitive complexity and decreasing maintainability. Especially if you additionally have nested conditions.
The most common suggestion for getting rid of switch statements is using polymorphism. See refactoring.guru or Refactoring.com. As React follows more the functional programming paradigm than the one behind OOP we need to apply a different solution. Namely dictionaries.
This blog post will show you how to refactor an application from using multiple switch statements to one dictionary, therefore decreasing the cognitive complexity and increasing the maintainability.
Let’s look at the problem
The following application represents a simple registration flow.
Each page has one input field. You can navigate between the steps using the NEXT
and BACK
buttons. In the end, you can submit the data you have entered.
So, depending on the currently active step we have to decide which component to render in the content area, whether to show/hide the BACK
button, which condition to use for enabling/disabling the NEXT
button, which text to show on the NEXT
button, and what exactly should happen when you click on one of the buttons.
The code could look like this:
You can see that each of the above mentioned features uses the same switch statement in order to tell the application how it should look and behave in each step. In addition to this, there are special conditions in the returned JSX
also depending on the value of the activeStep
state variable.
Imagine adding an additional step. You would need to touch all these places. Also, your IDE would not tell you which occurrences of the switch statement are not exhausted. This means whether you have implemented a case for each of the possible values of activeStep
. There is a way to enable this in TypeScript using the never
type (see this blog post). But again, additional code and additional complexity.
Let’s solve the problem
Now let’s have a look at how this problem can be solved utilizing a dictionary. In TypeScript, there is the type Record<Keys, Type>
. This type defines a dictionary of values of some defined type that can be accessed via a key. Whereas the type of the keys has to satisfy the constraint of being convertible to string | number | symbol
. In our case, we will use Enums.
In order to get rid of all the switch statements and conditions, we will create a dictionary with keys of the type Steps
(the same Enum we use for the activeStep
state) and values of the type StepConfig
. The latter will hold all the elements which differ per step.
Each entry of the dictionary represents a config with the needed elements for each possible value of activeStep
.
We can now retrieve the config for the current step using the bracket notation with the state variable activeStep
.
This leaves us with the following code without a single switch statement or condition. The stepConfigs
dictionary provides the correctly configured elements for the active step. Additionally, there are two utility functions that help us avoid describing the common properties of the BACK
button (renderBackButton
) and the NEXT
button (renderNextButton
) repeatedly.
Adding new steps is a lot easier as well, as there is now only one single place that needs to be touched, besides adding a new value to the Steps
Enum. Also, your IDE will show a lint error if you don’t implement a config for each of the Enum’s values, making it easier to catch mistakes early.
If I remove the config for the third step, I will get the following error message.
This blog post is published by Comsysto Reply GmbH.