React & Node.js: Level Up. Summary of the Meet-up
Developers often look for ways to be more efficient. Thankfully, JavaScript frameworks don’t restrict them in building their system and provide much freedom. However, with great power comes great responsibility. So, how to cope with problems encountered on your way? And what can help in optimizing performance? We discussed with Dmytro Hryshchenko, Full-Stack Software Engineer, and Grigor Oganesyan, an Advanced Software Engineer at Innovecs, during the React and Node.js: Level Up online meet-up on June 16. We have prepared a summary of the event covering the key points.
Grigor Oganesyan shared approaches that may help you solve problems occurring using Node.js:
- Observer pattern. Grigor suggests using a combination of an orchestrator and event queue as an observer pattern.
- Modules for business processes. Usually, we build models for entities. Suppose you have an e-commerce project online market. You have entities like orders, users, clients, and products. When your client buys something, you create an order and maybe change your user balance to decrease the number of items in stock. You will most likely do all this stuff in the order model. But is it correct? Maybe you want to build an order processing model that will do everything one by one? You do not call one entity-based model from another based model. You generate a new layer.
In our case, it should look like this: this password model takes whatever it needs from the user and notification models and changes the password. And do the change password thing, the notification sending, and increase the counter. We have all the functionality we had before, but now we do not have the circular dependency anymore.
There are also supportive approaches allowing to avoid problems using Node.js. Here are some of them:
- Automated unit tests. To make a unit test, you must build your nodes and methods by following the single responsibility principle. It would help if you mocked your notification model. If you build a unit test and have to mock something, think twice. Maybe you should do something differently, follow another approach. Automated unit tests are always a good idea. They bring more confidence once you write tests for your code.
- Code review. Indeed, it will not solve architectural issues. But those problems like SQL Dependency or any other did not surface due to our laziness. It’s part of our nature. And a reviewer doesn’t care whether you’re lazy or not. It would show you where you slipped up and what should be done differently. Sometimes it might help.
- API. Previously Grigor thought that it happens only with junior-level developers. Indeed, it is true for full-stack developers. Imagine that you have a client and a server. And a client asks to make a sandwich. API points response: double cheese, triple-sauce stake-salami sandwich.
Why? Because a full-stack developer built it. To avoid such cases, you should separate the client-side and API between two people even though they’re both full-stack developers. I suggest splitting processes between at least two developers when discussing client-server communication.
Dmytro Hryshchenko, a Full-Stack Software Engineer, talked about the key properties of React. It’s a well-known and widely used concept. Yes, we already understand why we need it and how to use it. But Dmytro saw some issues the developers had with this property quite often. That is why he has prepared some recommendations using it:
- Use it in each mapping operation in the renders of React Components.
- Use some unique id for each property, “index” might not be good enough.
- Can be used to trigger the remounting of the whole React Component.
Dmytro also touched upon the commonplace hook usage in React infrastructure. At the same time, he warns that you shouldn’t use hooks conditionally. If you need to introduce any condition — do it inside the hook. “Eslint-plugin-react-hooks” plugin can help you identify some misuses. When you add it to the project, it will show you all the potential cases where you might be wrong.
The most straightforward hook is the use state. Dmytro stresses that sometimes updating the state with a callback is vital. It is essential to rely on a previous value of the state. It might be just the regular render of the component.
Also, it can be helpful inside useEffect, where the current value of a state is not available since it is not provided in the dependencies array. It can help set the initial state if some heavy computation is required.
The speaker also calls for using the dependencies array in hooks correctly. Inside the dependencies array, when React tries to estimate if the value was changed or not, it provides the comparison. And this comparison is made by the following rules:
- Primitive values are compared by their values. Primitive values are strings and numbers.
- Objects, arrays, and functions are compared by their reference.
State batch updates. When React has introduced the feature of React hooks, and when we use state hooks so commonly, we can split the state into multiple independent parts. It helps to focus better on the business logic and split the state based on the business requirement instead of storing all the states in one object and dealing with this object each time when providing an update. So:
- A few parallel state updates will be batched together.
- Before React 18, a few parallel updates in any kind of callback (times, promises, event handlers) would cause a separate re-render.
- If you need no trigger re-render on each state change — use “flushSync.”
Now let us look at quite recent features of React that are related to the suspense. Here is what Dmytro recommends:
- Use lazy loading to show the content only if a user requests it. When you have the routing functionality with different pages, and you are not sure whether it will open some of those pages, you can apply lazy loading functionality with the default value of suspense to show the loading state. It will help you optimize your bundle and not to load all the code that most probably each user will not need while using your application.
- Take into account your Component’s pending and error state with Suspense and Error Boundary.
Dmytro also briefly touched on the topic of children API. Let’s look at how we can move further from that point. React has a dedicated children API that might be useful to modify the values you put as children inside your React component. So, you can not only render the children as they are, but you can enhance them with some additional functionality. You can conditionally render them. You can slice them, change places, and more. So, you can tweak them as a kind of array. Here is the list of methods that can be applied to the Children:
- “toArray” converts the children to a real array
- “count” provides the number of children
- “only” makes sure that only one child is provided
- “map” allows modifying each child
- “each” allows iterating over each child
One of the exciting things that have been added to React 18 is the functionality of transition updates and two useful hooks:
- “use Transition” allows prioritizing urgent updates and delaying heavier and less urgent ones.
- “useDeferredValue” helps in cases when you don’t have control over a value.
These hooks try to achieve the same goal but should be used depending on your situation. If you have whole control over the state updates and are the person coding the state update, you can choose “use Transition.” And suppose you want to receive control over the state updates that happen in some external component from a library that does not have direct access. In that case, you can use “useDeferredValue.”
The render props pattern is Dmytro’s favorite React pattern helping to replace almost every use case of the Higher-Order Component pattern that was extremely popular previously and was not too optimal from the usage and typization perspective.
In the end, Dmytro draws the audience’s attention to The State’s management of React. They are the following:
- Regarding the context, Dmytro suggests using context only when it comes to some global state that is rarely updated. For example, if you have some visual SIM dark or light that the user selects, it’s not expected to be changed often. Or if you have some user’s permissions, for example, received from a decoded token when a user authorizes to your system, you want to store those permissions and access them from any part of your application, but you don’t expect them to be often updated. Dmytro suggests putting the context only very close to the part of the application where you want to use it. Suppose you have a few pages inside your application, and the context belongs to the functionality of only one page. In that case, he suggests not to put it on the very top of your application but to put it specifically close to the page where it will be used. Because you have to remember that each time you update the value of the context, all those children will also trigger the render.
- UseReducer for complex state changes because it’s much more readable and easier to debug such functionality when you can clearly understand which action triggers what update.
- UseState only if a value change should trigger re-render, and useRef otherwise. Use should think about the update of each value inside your React component. You also have to ask yourself whether the update of this value will be needed for re-render, or it should be used just for some internal application logic. For example, if you are positive that the component has to trigger the re-render changing this value, you have to UseState. On the contrary, if that value is utilized for some further usage but should not directly trigger the update of the re-render, you can efficiently use such a value inside the reference of the component.
To sum up, you can always find a solution to your problem if you are passionate about your job. Just keep looking for new options and learning opportunities.