At the last meeting of our frontend dev community, Rafał talked about a piece of code he wrote in RST over four years ago. The code stood the test of time – it is well-thought-out and, therefore, worth revisiting. Moreover, it was an excellent opportunity to use it as an example of working with generics in TypeScript.
The code analyzed during the meeting was responsible for downloading data. Not a big deal, right? Well, the whole application is based on list and detail views. This is the most important part of the application.
Generics in TypeScript
The system is based mainly on react-redux and RxJS and used to handle async data fetch in store. The client required two things: fast response time, which we achieved through cache, and retries for requests that end in error.
Generics allowed us to make one system that handles all lists and tables in exactly the same way! There was no cache code duplication or repetition of requests.
How does the system work? A React component uses a hook responsible for fetching data from the store or sending an action to the store to fetch the data from the backend. The sent event goes to RxJS, which communicates with the service performing the appropriate query by selecting a service dedicated to a specific list. The data goes back to RxJS, and then, based on the data, an event is created for the reducer.
Nowadays, it is worth considering using a react-query for this type of function. But four years ago, there were no such solutions. And that's good because we could use the code to discuss how generics work in TypeScript!
Illegal states in TypeScript
The second part of the meeting was dedicated to illegal states in TypeScript, and then, consequently, the next topic was creating a business domain model. The types that TypeScript offers allow for a good mapping of business requirements in the project code. And thanks to understanding the topic of illegal states, our code will be cleaner and less prone to errors.
Illegal states can be easily visualized with an interface that models the functionality in such a flexible way that it allows the creation of objects that meet this interface in a way that is difficult to explain in the context of the application.
The discussed subject, without going into details, can be based on a simple example. Let's take a state contract for loading service responses like in the example below:
In an extreme case, we can create an object that meets the above interface:
It's hard to tell the loading state from the contents of this object in right now, isn't it?
This can be solved with a little refactoring and union of objects defining the allowed states:
In the context of business domain modeling, we discussed an example of a user interface that, in a given use context, can be of two types, distinguished by a specific group of fields.
Based on the above example, we can imagine how to model an interface that meets business rules, and allows for the creation of illegal states. In such cases, it is helpful to separate parts of the overlapping fields from both business cases to the general interface, and prepare dedicated types for business cases to extend this interface. The union of prepared types defines the final interface that satisfies the business rules and is safe in terms of illegal states.