Usage with effector-react
TypeScript is a typed superset of JavaScript. It became popular recently in applications due to the benefits it can bring. If you are new to TypeScript, it is highly recommended to become familiar with it first, before proceeding. You can check out its documentation here.
TypeScript has a potential to bring the following benefits to application:
- Type safety for state, stores and events
- Easy refactoring of typed code
- A superior developer experience in a team environment
#
A Practical ExampleWe will be going through a simplistic chat application to demonstrate a possible approach to include static typing. This chat application will have API mock that load and saves data from localStorage.
The full source code is available on github. Note that, by going through this example yourself, you will experience some benefits of using TypeScript.
#
Let's create API mockThere is a directory structure inherited from the feature-sliced methodology.
Let's define a simple type, that our improvised API will return.
Our API will load and save data to localStorage
, and we need some functions to load data:
I also created some libraries to generate identifier and wait to simulate network requests.
OK. Now we can create effects that will load messages.
OK, now we are done with the messages, let's create effects to manage user session.
Really, I prefer to start design code from implementing interfaces:
Also, to generate a usernames and don't require to type it by themselves, import unique-names-generator
:
Let's create effects to manage session:
How we need to import this effects?
I surely recommend to write short imports and use reexports.
It allows to securely refactor code structure inside shared/api
and the same slices,
and don't worry about refactoring another imports and unnecessary changes in the git history.
#
Create a page with the logicTypical structure of the pages:
I recommend to write code in the view layer from the top to bottom, more common code at the top. Let's model our view layer. We will have two main sections at the page: messages history and a message form.
OK. Now we know what kind of structure we have, and we can start to model business-logic processes. View layer should do two tasks: render data from stores and report events to the model. View layer doesn't know how data is loaded, how it should be converted and sent back.
Now we can implement components.
I split MessageForm
to the different components, to simplify code:
#
Manage user session like a ProLet's create a session entity. An entity is a business unit.
Now in the page we can implement login or logout features. Why not here?
If we place login logic here, we will have very implicit scenario,
when you call sessionCreateFx
you won't see code called after effect.
But consequences will be visible in the DevTools and application behaviour.
Try to write the code in as obvious a way as possible in one file, so that you and any teammate can trace the sequence of execution.
#
Implement logicOK. Now we can load a user session and the messages lists on the page mount. But, we don't have any event when we can start. Let's fix it.
You can use Gate
, but I prefer to use explicit events.
Just add useEffect
and call bound event inside.
Note: if you don't plan to write tests for effector code and/or implement SSR you can omit any usage of
useEvent
.
At the moment we can load a session and the messages list.
Just add reaction to the event, and any other code should be written in chronological order after each event:
After that we need to define reactions on messagesLoadFx.done
and messagesLoadFx.fail
, and the same for sessionLoadFx
.
OK. Session and messages loaded. Let's allow user to log in.
Now we'll implement a logout process:
Note: most of the comments wrote just for educational purpose. In the real life application code will be self-describable
But if we start the dev server and try to log in, we see nothing changed.
This is because we created $loggedIn
store in the model, but don't change it. Let's fix:
Here we just reexported our custom store from the session entity, but our View layer doesn't change.
The same situation with $userName
store. Just reload the page, and you'll see, that session loaded correctly.
#
Send messageNow we can log in and log out. I think you want to send message. This is pretty simple:
But if in the tsconfig.json
you set "strictNullChecks": true
, you will see the error there.
It is because store $session
contains Session | null
and messageSendFx
wants Author
in the arguments.
Author
and Session
are compatible, but not the null
.
To fix this strange behaviour we need to use filter
there:
I want to focus your attention on the return type form is {author: Session; text: string}
.
This feature called type guard
and allows Typescript to reduce Session | null
type to more specific Session
via condition inside the function.
Now we can read this like: when message should be sent, take session and message text, check that session is exists, and send it.
OK. Now we can write a new message to a server.
But if we don't call messagesLoadFx
again we didn't see any changes,
because $messages
store didn't update. We can write generic code for this case.
Easiest way is to return sent message from the effect.
Now we can just append a message to the end of list:
But at the moment, sent message still left in the input.
#
Deleting the messageThere is pretty simple.
But you can see the bug, when "Deleting" state doesn't disable.
This is because useList
caches renders, and doesn't know about dependency on messageDeleting
state.
To fix it we need to provide keys
:
#
ConclusionThis is simple example of application on effector with React and TypeScript.
You can clone this effector/examples/react-and-ts and run this example on your computer.