Understanding React Hooks
Hooks are a relatively recent addition to React. They represent a major shift in the way we use state, lifecycle and other React concepts within components. In this article, we will dive into the motivations behind React Hooks, explore the different built-in hooks, and learn how to re-use functionality by creating our own custom hooks.React is hands down the most popular front-end framework in use on the web today, clearly ahead of competitors such as Angular and Vue. I believe that React's simplicity is the main reason for its popularity. It abstracts away having to deal with DOM elements and provides a simple and lightweight API to rapidly build single-page apps. However, there have been some aspects of React which were slightly confusing and difficult to use. One of the main complaints over the years has been that to use React features such as state and lifecycle methods, you had to use class-based components rather than function-based ones. We will dive deeper into these issues further in this article. To address these issues and provide a means to use React features in function-based components, the React team has introduced Hooks . It has been just over a year since Hooks were released in React 16.8, and they generated a lot of buzz at the time - every React dev was gushing about how things were so easy now with React Hooks! π€ I was fortunate enough to be able to work on many greenfield projects in the past year, so I could start using Hooks right away and not have to deal with any of the shortcomings and confusion of class-based components anymore. It was only when I encountered massive class-based components 'in the wild' in legacy applications, did I realise how hooks simplify things and truly encourage cleaner code. β¨ Simply put, React Hooks are just functions that when called from a function-based component, allow it to "hook into" certain React features. This is a game-changer, as it lets you build advanced features using function-based components, which are simpler and easier to reason about. There are various built-in hooks provided by React. useState and useEffect are the two most used ones. We will learn about these and other built-in hooks in upcoming sections. Since hooks behave just like any other JavaScript function, you can also define your own hooks to reuse functionality across components within your app. There are, however, some rules to follow when using hooks. Some of the main pain points when using class-based components are: Whenever you create a class-based component, it has to inherit from React.Component . This means that if you want to add a constructor with some logic to execute at instantiation time, you will always have to call super(props) first. Additionally, because of the way the this keyword works in JavaScript, event handlers are required to be specifically bound to the current context. π€―This further increases the amount of boilerplate needed. An alternative to this is to use arrow functions(introduced in ES6), which will have access to their bounding scope. That, however, is just one more thing to keep in mind. In class-based components, the state property is always an object and can not be any other primitive. This means that whenever you need to update a single value in your state, the setState call will merge the new value with the current state object. This makes it difficult to separate concerns and to potentially extract reusable code. In this example, we have two related properties firstName and lastName , co-located with an unrelated color property in the state object. If they could be separated, any logic around handling name changes could potentially be extracted out. However, this is not possible with a class-based component. As a consequence of how class-based components work, the only way to trigger code on state changes is via lifecycle methods such as ComponentDidMount and ComponentDidUpdate . In this example, the Counter component keeps the page title in sync with the counter value. To accomplish this, we've had to repeat the same code in componentDidMount (to set the page title when the component loads), and in componentDidUpdate (to set the page title each time the counter is incremented). If we wanted to reset the title when the Counter is removed from the screen, we would have had to repeat the code in componentWillUnmount as well. Now that we have understood all these pitfalls, let us explore how React Hooks provide solutions to the above problems. The useState hook provides a means for function-based components to maintain state. This state does not necessarily have to be an object - the useState hook supports any primitive so you can use numbers, strings, booleans or arrays as well. Here is a simple example of how to use it. It uses the Name component we have seen in the previous section, now converted to be function-based. The different state variables have now been decoupled and can be managed independently. The Name component will re-render whenever the values of the state variables change. The useEffect hook can be used to trigger side effects in function-based components. Earlier in the article, we saw how class-based components use lifecycle methods to allow for reacting to state changes. Let us now see how this can be done in a much more simplified way using a combination of the useState and useEffect hooks. The useEffect hook we have used here takes two parameters: In the example, the effect is triggered each time the value of the counter variable changes. It sets the document title to the current counter value. This handles two scenarios - the initial load scenario covered by componentDidMount , and the subsequent update scenario covered by componentDidUpdate . Additionally, the function passed into useEffect can optionally return another 'clean up' function, which can be used to provide componentWillUnmount behaviour. If we wanted to perform an action just on initial load, we can use the useEffect hook with an empty dependency array, like so: Due to the asynchronous nature of state changes, it can not always be guaranteed that any functions declared within a component will have the most up-to-date values of state variables. This manifests most commonly when using an event handler to update the state, when the new state is based on the previous value. In the example below, the incrementCounter function may not always have the newest value for the counter . When declaring a function that depends on variables outside of it, we can make use of the useCallback hook. This will make sure that the function is updated each time any of the variables it depends on changes. This will ensure that the incrementCounter function is updated each time the counter value changes. React's Context API provides an easy way to create global state that can be accessed by components anywhere in the tree. This can be used to implement features such as theming. Let us consider a simple example to understand this better. The entire app is wrapped in a ThemeProvider to provide access to the theme value. We would then consume the value in our components using a <ThemeContext.Consumer> tag which provides the value as a render prop. There are a couple of downsides to this approach. We can now access the context using the useContext hook in the body of the component. This also automatically subscribes the component to changes in ThemeContext , which means the component will re-render each time ThemeContext is updated Earlier in the article, we discussed the problem of monolithic state with class-based components, and how the useState hook solves that problem. However, there are situations where different pieces of state are interlinked, and it does make sense to have them in an object. For these use cases, we can use the useReducer hook. This hook takes a reducer function, and an optional init function, and returns a state object and a dispatch function. The dispatch function can then be used to trigger actions which will update the state object. To understand this better, let us go back to the Counter example, now implemented with useReducer . It now also displays a message based on the counter value. This is a very simple example but in the real world, you could have much larger components with complex state and transitions. By using the useReducer hook, you can effectively separate the stateful logic from the presentation logic. The reducer is a pure function and therefore is easily testable with unit tests. The useRef hook is used to create a mutable ref object, whose current property will store the initial value passed into the hook. This object will persist across re-renders. The most common use case for ref s is for accessing child nodes in the component. In this example, we use a ref to focus the text box on render. As we have discovered through the article, hooks are just like any other JavaScript function, albeit with a few ground rules. This means that we can also define our own hooks to encapsulate and reuse logic between components. Within the body of a hook function, you can call other hooks to use their functionality as well. Here is an example of a custom useFetch hook that can be used to make HTTP calls. If you're using an API that needs an auth token passed in, you could extend the useFetch hook to add Authorization headers to each request, thereby saving you the hassle of having to do that on every fetch. Another common use case is for setting up and cleaning up event listeners. In this article, we have learnt about React Hooks in detail. We understood the differences between class-based and function-based components and explored the pain points of using class-based components - lots of boilerplate code, monolithic state and the inability to watch for changes outside of lifecycle methods. We have learnt how to use the various built-in hooks with examples: In addition, we have also learnt how to create custom hooks to encapsulate and reuse functionality. We have seen how this can be useful through the examples - useFetch and useEventListener . React Hooks have been one of the most important tools in my toolbox ever since their release. I hope this article has made it easier for you to understand them and made them a useful tool for you as well! πͺHappy coding! π The Hooks Overview in the React docs is a great starting point for your React Hooks journey. This video from ReactConf 2018 where Sophie Alpert and Dan Abramov introduce React Hooks provides a really good explanation of the motivations behind hooks. This comprehensive collection of custom React hooks is a great resource for finding code to reuse. This article by Fullstack D3 author Amelia Wattenberger has a great explanation of the mindset change needed to be able to think in React Hooks.