Common Component’s State Issue
Let’s start by looking at this simple component, which receives props (a superhero’s name) and has an internal state (the superhero’s health).
Up until recently, if you ran this code you would get no compile-time errors. However, in runtime, you would get an error about accessing a property on null because the state was not defined when we tried to access
this.state.health. This can happen when you forget to initialize the state for the component in the constructor. Thankfully, the latest version of Definitely Typed @types/react, you will now throw an error on compiling.
Ok, let’s set that state in the constructor!
What’s the problem with using the constructor?
Here is the same component as before, but now we are initializing the state in the constructor.
There are 2 issues with this code:
- Unnecessary boilerplate — We are calling the constructor function, defining the prop’s type again and calling super(props). All this just because we want to initialize the state…
- We are leaving the state open to mutations — The first rule of React is we do not talk about mutations. The second rule of React is WE DO NOT TALK ABOUT MUTATIONS. Ok, we do talk about it, but as something to avoid at all costs. In this scenario, I can do the following without getting an error:
this.state.health = 90;Indeed, my component won’t render again and I will still see the health as 100, but no errors will be thrown since we did not protect the state attribute of the class.
And indeed, starting from @types/react 16.4.3 this code will throw the following error:
Cannot assign to ‘state’ because it is a constant or a read-only property
OK, I’m convinced, what’s the solution?
Luckily, in Typescript we can use the
readonly attribute on a class property. This gives us a clean and clear way of protecting the state object from mutations (line 10):
I know what you’re thinking
After reading the example above, the first thought that ran through your head was: “Nice!”. The second thought that ran through your head was: “But what if I need to initialize my state with some logic from the props?!”. Well, there is a nice solution to this:
As you can see in line 12, we are calling the
getInitialHealth (which is a pure function) with the component’s props and if the superhero’s name is Spiderman, his health is 0, if it’s someone else, it will be 100. I really like this approach since you are only referencing props because you actually need them, not because the constructor function demands them.
I don’t have time for this! QUICK FIX ASAP!
There is a good chance that you now have bunch of failing projects that need fixing. If you are really short on time (even though these fixes are really small and don’t take a lot of time to change), there are 2 things you can do. Either:
Pin the @types/react version package in your package.json to 16.4.2 before these new typings were introduced.
Define the state property in the class so the compiler ‘knows’ that it is going to be set in the constructor (line 10):
The ‘new’ way of setting the state in a Typescript React component is really nice and protects your state object from mutations. Sure, this might not be ‘critical’ for expert devs (we are all still humans, right?), but for new devs this can definitely help prevent common mistakes which are usually pretty hard to find.