August 03, 2020 React Hooks: Why You Should Be Hyped Up When React v16.8 launched, it introduced the world to an exciting new feature: React hooks. Let’s find out why we need them, and what problems they solve. Tseko Tsolov Since v16.8 stepped up on the web development scene in Feb 2019, the React world changed dramatically. In this version, the React team introduced an exciting new feature — Hooks. React Hooks became a hype that caught everybody’s attention. Some challenge their effectiveness, some encourage the developers to rewrite the old class components to a functional one as soon as possible. Let’s find out why we need them, and what problems they solve. What are React Hooks? Hooks allow you to use state and other React features without writing a class. They don’t even work inside class components. Honestly, classes are a little bit confusing and, as the React team states, are a large barrier in learning React. You have to understand how the “this” keyword works and remember to bind event handlers. It is also observed that classes don’t minify very well, which makes hot reloading very unreliable. Another huge advantage is that hooks give you the power to reuse stateful logic and share it among other components without changing your component hierarchy. Hooks let you split larger and harder to understand components into smaller chunks based on each piece’s functionality, rather than the lifecycle method limitations required by the good old classes. Let’s dive into some code and see the power of hooks in action. Assume that we would like to be aware of the current mouse position and display it to the user. Sounds simple and easily maintainable with the good old class components. But what if we want to reuse the logic across other components in our application? It may seem trivial as there are patterns like Mixins (considered harmful according to the React team), HOC’s, or Render Props. If that’s your assumption, you’re definitely right, but imagine having an option that eliminates all the known flaws of these popular techniques. No more ending up with giant components, “wrapper hell,” or duplicated code. Instead, you can embrace the possibility of reusing stateful logic, without changing the component hierarchy. Sounds tempting to set aside those complex and hard to comprehend patterns, right? Discover the power of hooks by this simple example: const CursorPositionComponent = () => { const coordinates = useCursorPosition(); return ( <span> Cursor position is X:{coordinates.x} Y:{coordinates.y} </span> ); }; The code above is quite easy to read and it is pretty clear what it does. It takes the cursor coordinates from the useCursorPosition and rerenders on every mouse move. But, what’s the magic behind the useCursorPosition function? It’s actually a custom hook. Let’s see how we can implement it and understand more about the predefined hooks “useState” and “useEffect” provided by React. const useCursorPosition = () => { const [coordinates, setCoordinates] = useState({ x: 0, y: 0 }); const handleCursorMove = e => { setCoordinates({ x: e.clientX, y: e.clientY }); }; useEffect(() => { window.addEventListener("mousemove", handleCursorMove); return () => { window.removeEventListener("mousemove", handleCursorMove); }; }, []); return coordinates; }; All built-in hooks that the React team ships start with “use,” followed by the main name of the hook function. Diving deeper into this new pattern, you’ll notice that you can implement your own custom hooks, and it is a good practice to follow that naming convention as well. The “useState” Hook The “useState” is a function that receives the initial state as a parameter, which is not necessarily an object. Actually, the value of the initial state can be of any type, a boolean, a string, a number, etc. The “useState” hook returns an array of two elements. The first element is the current state, while the second is a function that allows us to overwrite that state and manage it. In our example, the state is represented by coordinates with an initial value of {X:0,Y:0}. On every “mousemove” event, we call “handleCursorMove,” which executes “setCoordinates” and updates the state with the new X and Y values. There’s an important difference between the class-based “setState” approach and the function returned by “useState”. The state managed by “setCoordinates” in our example is not merged with the old one. Instead, it’s overwritten with the new value passed to the “setCoordinates” function. Another major difference is that we aren’t limited to only one state in functional components as we are with class-based ones. We can call “useState” as many times as we want and define multiple states. There’s an option to create different states that are responsible for managing the X and Y coordinates. The “useEffect” Hook In order to call “handleCursorMove,” we need to add a listener that fires the function on every mouse move. It needs to be added only once throughout the entire life cycle of the component and must be removed when the component unmounts. Sounds like we need something that resembles “componentDidMount” and “componentWillUnmout” lifecycle methods. Since these methods are applicable only to class-based components, what’s the solution in our case? That’s the place where “useEffect” hook steps up. Its purpose is to manage side effects, such as making HTTP requests, setting up event listeners, etc. How does it work? It takes a function as an argument. This function will be executed on every render cycle after the component has been mounted to the DOM. It’s a good place to set up our event listener, but there’s a catch here. Since “useEffect” runs on every re-render of the component, how do we prevent it from attaching a dozen event listeners? Actually, the “useEffect” takes two arguments, the second argument is an array of dependencies of that function that we passed as the first argument to it. The variables we passed to the array decide if the effect reruns or not. If any of them has been changed throughout the lifecycle of the component the effect runs again. So it looks like useEffect can be used instead “componentDidUpdate” also. Great. But if we pass an empty array here, it guarantees that there is no data that could change. The effect will be only triggered once, after the component mounts for the first time, so the “componentDidMount” behavior is easily achieved. It’s important to always pass all the variables that we use in our effect and participate in the React data flow, as elements of the dependency array. That includes props, state, and anything derived from them. What about cleaning up the effect? This can also be achieved by the “useEffect” hook. It can return a function that runs only when any of the dependencies have been changed and don’t execute on initial render. In our case, since we have no elements in the dependency array, it will run only once, when the component unmounts. To sum up, the “useEffect” can replace “componentDidMount”, “componentDidUpdate” and “componentWillUnmount” and serves the same purpose as these life cycle methods. Performance and Optimization (useCallback, useMemo) Before proceeding, let me warn you that performance optimizations aren’t “free” and sometimes they’re not as beneficial as they seem. They come with a price and it’s a developer’s responsibility to assess the benefit over the cost. When it comes to performance optimization, React provides useCallback and useMemo for our benefit. Both of them receive a function as a first argument and a dependencies array as the second. It returns a new value only when at least one of the dependencies changes. The core difference is that useCallback returns a function without calling it, while the useMemo calls the function and returns its result. The useCallback and useMemo are built for two major specific reasons. The first one is the referential equality in JS ( {} === {} is false, () => {} === () => {} is false as well ), the second one is to tackle the computationally expensive calculations. Take a look at the following example: const Child = ({ firstOption, secondOption }) => { const options = { firstOption, secondOption } const [state, setState] = useState(options) useEffect(() => { const newOptions = modifyOptions(options) setState(newOptions) }, [options]) // we want this to re-run if firstOption or secondOption change return <Demo options={state} /> } const Parent = () => { return <Parent firstOption={"test"} secondOption={3} /> } The issue here is that options in this example will be new every time React re-renders thanks to the JavaScript specific equality check. One way to fix this is to declare the options inside the useEffect as follows: const Child = ({ firstOption, secondOption }) => { const [state, setState] = useState({ firstOption, secondOption }) useEffect(() => { const options = { firstOption, secondOption } const newOptions = modifyOptions(options) setState(newOptions) }, [firstOption, secondOption]) // we want this to re-run if firstOption or secondOption change return <Demo options={state} /> } This is a quite good solution, but it won’t help if firstOption and secondOption are non-primitive (arrays, objects, or functions). That is exactly the place where useCallback and useMemo shine. In this case, the actual solution will be: const Child = ({ firstOption, secondOption }) => { const [state, setState] = useState({ firstOption, secondOption }) useEffect(() => { const options = { firstOption, secondOption } const newOptions = modifyOptions(options) setState(newOptions) }, [firstOption, secondOption]) // we want this to re-run if firstOption or secondOption change return <Demo options={state} /> } const Parent = () => { const firstOption = useCallback(() => {}, []) const secondOption = useMemo(() => [1, 2, 3], []) return <Child firstOption={firstOption} secondOption={secondOption} /> } The firstOption and secondOption are now memoized which will not trigger unnecessary useEffect calls inside the Child component. (You can find additional information on hooks performance here.) The “Catch” So what’s the catch with hooks? Once you get familiar with them, they’re pretty easy to adopt. But, there are certain rules you need to follow when using them. Hooks can be called only on the top level of your components. They cannot be used inside loops, conditions, and nested functions. And, they can be only called inside React functional components or another custom hook. It’s pretty easy to follow these rules since we have a linter provided by the React team to help us using hooks flawlessly, by enforcing the rules automatically. You can find more on that here. React Hooks Summary In this article, we went through some of the most important and basic hooks that React provides. Once you get deeper into hooks you can explore more of them, such as “useReducer,” “useContext,” “useRef,” etc. Hooks are totally optional and everything you already learn and know about React is still relevant. Hooks are easy to comprehend and provide an option to reuse common pieces of logic between components. This makes “render props” and HOCs patterns almost obsolete. They allow you to use React without having to write a class, binding methods, and dealing with “this” keyword tricky behavior. Data fetching, event listeners are no longer split across different methods such as “componentDidMount”, “componentDidUpdate” and “componentWillUpdate” and you can easily organize the logic into reusable isolated chunks. Image Source: Tatiana Rodriguez on Unsplash Tags DevelopmentCross Platform Share Share on Facebook Share on LinkedIn Share on Twitter Dawn of the Progressive Web Apps What is a Progressive Web App, and what new options do they provide smart businesses? Download Share Share on Facebook Share on LinkedIn Share on Twitter Sign up for our monthly newsletter. Sign up for our monthly newsletter.