Loading state
Imagine a situation where you have to send a request to a server to get data.
This operation can take some time so it can be a good idea
to display a spinner with some text to inform a user that
something is happening (instead of displaying a blank page).
To do this we can create a variable loading
and write in our
template logic to display one of two views: loading view or data view.
More states
In a real scenario, we have more than just two states. When we display some data we want to allow users to filter, sort or paginate it. In this case, it would be nice to display some kind of spinner when data are updating. But what about errors? A server can also respond with a temporary issue (no connection to the database, internal server error, or timeout). It also would be nice to display.
Similar to before, we can do this by adding new variables to our view: error
and refreshing
.
But this does not cover all needs.
We also want to store data about the error and also items to display.
This makes our component bigger and less readable.
State object
To resolve described problems with too many variables we can create something that is called state object. This pattern is about creating a plan JavaScript object and storing all data needed to display the view in a single object.
readonly
is necessary to block possibility to mutate a state.
Now we can come back to our component and replace all properties with single-state objects. Thanks to that our component is more readable.
Problem with types
Imagine a situation where we want to create an effect to log an error message.
When an error occurs we want to get the errorData
property.
We should not have a situation where error
is set as true
but errorData
is null
.
TypeScript will throw an error errorData is possibly null only when you have strictNullChecks
or strict
flag turned on.
Union states
In a situation where we know that error
and errorData
properties are connected
(when error
is false
then errorData
is null
and when error
is true
then errorData
is defined)
we can create interfaces for each state and connect them with Union types.
Thanks to that separation, we tell TypeScript that ComponentState
is one of two states: NoErrorState
or ErrorState
and TypeScript will know when error
is true
then errorData
is defined.
TypeScript knows that when error
is true
then state
inside if
block has type ErrorState
(TS Playground).
Split into more states
When we know that we can create many states and connect them into a single type we can define all possible states with interfaces.
Checking current state
Let us go back to our effect where we want to check current state. To do this we have to check which properties are defined and which are not.
This if
statement is not readable and each time we have to know what to check.
Keyword in
is used as a type guard.
Because only LoadedState
has data
property TypeScript
knows that inside if
variable currentState
have to be LoadedState
(TS Playground).
Named states
To make easier checking current state we can create a special property which will hold current state name.
We can create a interface to share base properties. This is nice to have but not required.
Now we can refactor the previous function and use simple checking of name
property
(TS Playground).
Comparing writing templates
Let us come back to our templates from the beginning. When we had individual properties for each state our template looks like below.
But when we migrated to the named states the template has to change. We cannot look now for individual properties. We have to now write a template where we will check a current state and for each state we need to have a template.
We can also handle many states at single case. It approach will be helpful where two states have a very similar look.
Comparing changing state
When we take a loot into the code from the beginning we can see that we had to “reset” each property one by one. It it very easy to make a mistake and forget to change a variable to original value.
In situation where we will use named states the code is much more shorter and most important more readable.