ReactJS library is one of the most popular solutions to build great web applications, but it’s not the only one. There are libraries similar to each other in terms of problems they are trying to solve and there are libraries trying to solve a specific problem. After reading this article you’ll get a better understanding of ReactJS and how to answer interview questions properly.
What is ReactJS?
ReactJS is a generic purpose library which is responsible for UI updates being as fast as possible to your application users and that’s it! It’s not a framework combining routing possibilities, sophisticated data management, providing additional API to solve non-UI related challenges.
However with ReactJS as a core of your framework, or any framework you can build an entire ecosystem of solutions that helps building great web applications faster. See NextJS as an example.
Understand the problem the hooks are trying to solve
React hooks are utility tools/functions that can be used inside components to share some pieces of logic between multiple React components. Hooks can use other hooks inside like useState or useMemo. That’s the basic explanation.
However, the main goal of React hooks is different. They should be used to avoid:
- heavy data computation when re-rendering React components for any reason,
- losing the data value reference causing re-renderings of a component or its children components.
Having this in mind will help you develop React web applications more efficiently, because this way you will know when to use React hooks and when not to use React hooks.
Each hook should have a name starting with its use prefix. This indicates to the ReactJS library that a given function is a hook and can use ReactJS features, like other hooks!
What kinds of hooks are available by default in ReactJS?
Before explaining when to use or when not to use React hooks, let’s take a look at a bunch of hooks provided by the ReactJS library:
This hook is meant to store any value in the “component memory”. That means that, once the value is set there, the reference to the value does not change between component re-renderings. This impacts the performance of the component code. However if the useState hook is used too many times in one component, then it might be an indicator that a component got too complex and we may lose control over what’s causing a component to re-render. In such situations you should extract some logic into a custom hook or create a child component that handles its state separately.
You can set a state in 2 ways:
- directly, by passing a desired value,
- indirectly/asynchronously, by passing a callback function that returns a new value.
Usually, you will set a value directly, but there are cases where you need to set a value based on a previous state.
Following that approach, you’ll always make sure you’re updating a value based on the most correct final state.
Whenever you need to handle any side effects that are not directly related to the component state — such as fetching data, sending data, showing notifications, or attaching event handlers to DOM elements — you should use this hook. This hook can expect a cleanup function to be returned, which is invoked when a component is about to be unmounted from the DOM. This way you can do things like canceling fetch requests or removing event handlers from DOM elements.
This hook requires passing 2 params:
- a callback function
- a list of dependencies that may trigger the callback you passed.
This hook is similar to useEffect in terms of initialization, but the purpose is different. It is useful for keeping a reference to a function/callback between each render. This approach helps avoiding re-rendering of children components using the function wrapped with useCallback hook.
Sometimes useMemo and useCallback are confusing for developers, especially those learning React. It’s because the purpose of them is the same: to keep a reference to a value.
However a useMemo hook is a bit different from useCallback because it expects some value to be returned from the callback function.
You will want to use the useMemo hook whenever you need to make some complex calculations inside the component, like filtering a huge list of elements based on active filters, if you’re building a highly interactive informational dashboard.
Now you know the difference between useCallback and useMemo, but useRef is even more specialized because it can keep a reference to any value no matter the state changes or props changes. For instance, you want to initialize an internal function once based on primary props or state when a component is initialized and you don’t want to recreate it with every re-rendering. You can use useRef hook for that and use that function anytime during a component instance lifecycle.
But the most popular way of using this hook is to keep a reference to a DOM element so you can attach event listeners to it once, like setting a focus on some input when an element is mounted to DOM structure.
This sets up a reference to a DOM element once it’s mounted to DOM, plus sets a focus on an input element. Without that you might end up trying to focus an element that has not been inserted into DOM yet.
In the ReactJS world there are 2 parties:
- one: using Redux for state management all the time, no matter if it’s really needed,
- two: using context to share some state between multiple components.
The useContext hook is meant to pass down some values to avoid props drilling from the very top parent component to a deeply nested children component. It can be used to do some sort of application state management, but it’s not as complex as Redux and it’s great for simpler use cases. Let’s take a look on how to start using it:
In the example above, you can pass the value of currentUser to a deeply nested component. This way ComponentOne and ComponentTwo don’t have to know that they need to pass down some value from a parent component (App).
What you need to remember is that context works great with primitive values like string, numbers, or arrays of primitive values. Functions and objects are trickier because you may end up re-rendering the entire DOM structure of the App component. In order to avoid that, I suggest making objects immutable by using the “Object.freeze” function (or using some immutable libraries).
The hooks mentioned above are the most popular hooks for daily usage. But there are more hooks available! To read about the rest of hooks, take a look at this page.
The other hooks are usually used in very specific use cases, which cannot be solved by the hooks listed above.
When to use and when not to use React hooks?
Hooks help make feature development easier, but sometimes using/not using hooks may lead to unexpected component behavior, such as too frequent re-renderings or incorrect data passed down to children components.
When to use hooks?
So when should you use hooks instead of helper functions? You should use hooks when:
- you want to extract parts of logic using effects and internal state management and share it between components. When shared between components, the components can react to value changes of hooks’ outputs.
- you want to add some event listeners to DOM elements exposed in multiple components, for instance, if you want to listen to clicking outside of a component container. By using a hook you can externalize implementation of it outside of a specific component, reducing some amount of duplicated code. Another example would be initializing some 3rd party scripts on a page/view component level.
- you want to avoid re-rendering components any time a reference to a value changes, but the value itself contains the same data as before (in the case of objects recreated because of some props/state changes).
- you have to make a costly data manipulation and you want to avoid doing it on every re-render.
When not to use hooks?
Hooks are great, but using too many hooks in a component can complicate things.
For instance, using useCallback to freeze the reference to a callback when it’s used as onClick handler on <button> HTML element is too much. That’s because the <button> element is a native HTML element and isn’t controlled by React the same way as React components.
It’s also not a good idea to use a hook when it has a really complex logic that causes a lot of internal state updates and exposes updates values outside of it, leading to some non-trivial performance bottlenecks.
Imagine a situation where you have a hook that helps you control the scroll position of an element and, based on that position, you’re returning some object with data. As scroll events occur very, very frequently, a component may render a terrific amount of times in the meantime. In such cases, extracting such logic to a hook may not be the best idea.
Performance improvements in React code
I mentioned it earlier, but using hooks can help with improving the application performance. Keeping the same reference to a value is very important. There are also other ways to improve performance:
1. lazy loading components — us React.lazy to load components when needed only. That way, you can make sure that an application will load only the minimum amount of code necessary to display an application at a given state. Once a user makes an action in the application, ReactJS library will attempt to load the missing pieces of code, displaying some fallback view in the meantime.
You can see how it works in the code sample above. We’re loading lazily components with React.lazy and use the Suspense component to provide a fallback view when components are loaded. One important note: You cannot import components that are exported from a file as named exports: export const OtherComponent. You must use a default export instead:
Most developers are OK with that limitation, even if they prefer consistent component naming convention when importing them from other files.
2. **********************************************memoizing components********************************************** - you can memoize components using the React.memo function. This means that a component wrapped with React.memo will not re-render until props changed or a validation callback allows it. Let’s see how it works:
The validation callback function takes 2 params: oldProps (props from the previous render) and the newProps (props from the new render).
If the validation callback returns true, then it means a component should not re-render (and otherwise, it should re-render).
This callback function is especially useful when comparing objects passed through the props, because you don’t have to use React.memo when sending primitive values (strings, numbers, boolean values) to a component. Strict comparison implemented by ReactJS will handle it properly.
After reading this article you should feel ready to build great ReactJS apps and be able to explain all the tricky parts of ReactJS library that may appear during the interview for a ReactJS developer role. Some key concepts we covered include:
- what is the purpose of ReactJS hooks?
- what are the standard ReactJS hooks?
- when and when not to use them,
- how to write performant code
- how to optimize ReactJS apps for better performance.
I hope you liked this article. Feel free to reach out in case of any questions.