React Hooks 101
A quick reference to commonly used hooks
I had a fun coding challenge recently where I had to build out a feature with React. It was definitely a nice change from a typical algorithmic coding challenge. The deliverable stated that I could only use functional components. But wait! I thought functional components couldn’t hold state. Incoming React hooks!
What are Hooks?
React hooks were introduced in 2018 as a way to give functional components, well, more functionality with state. Traditionally, state is stored and managed in class components. Code held in class components can get long and convoluted considering all of the lifecycle methods and state you might have to write. The hooks system allows us to write more reusable code rather than the inheritance nature of class components. But just remember “Hooks don’t replace your knowledge of React concepts”. They build on top of concepts like props, state, context, refs, and lifecycle.
Config
React hooks are a native feature of create-react-app
. To use hooks, all you have to do is import React and destructure the hooks you want to use as shown in the gist below.
Commonly Used Hooks
There are ten native hooks available for use. They include: useState
, useEffect
, useContext
, useReducer
, useCallback
, useMemo
, useRef
, useImperativeHandle
, useLayoutEffect
, and useDebugValue
. There’s also an option to write custom hooks. For custom hooks, we can abstract hooks into a function that starts with the convention “use____
”. I will talk about two common hooks below.
Some basic rules to know when using hooks are to:
- Don’t use hooks inside vanilla JavaScript functions.
- Use hooks at the top-level — meaning, don’t use them in loops, conditions, or nested functions. This is because is it possible to use many hooks in one component and the order of them matters. So we want to be able to preserve state correctly.
useState() — initialize, reference, or update state
useState
is the function that lets you use and manipulate state in a functional component. It uses array destructuring to allow for use two functions to reference a piece of state and to update state. The initial value of state is passed in as an argument of useState
. A common use case would be to use the second element, the setter function, as a callback of an event handler. That setter function would be the equivalent of setState
in a class component.
In the gist below, we can compare how state would be handled in a functional and class component. One benefit of useState
is that only one line of code is necessary to do what lines 5 to 7 is doing.
useEffect() — similar to lifecycle methods like componentDidMount() or componentDidUpdate()
The next hook we’ll explore is called useEffect which is useful for actions to take when rendering and/or re-rendering. One common use case is to make requests and fetch data with useEffect
. The use of componentDidMount
is not possible in functional components. There are three scenarios to consider when using useEffect
. This hook can run code automatically when the component:
- Initially renders
- Initially renders and also whenever it re-renders
- Initially renders, whenever it re-renders as well as when data is changed
Another way to think about those three scenarios is if you want to use useEffect
with or without cleanup — meaning, do you want to express any side effects like running additional code after the initial render? If you decide to use useEffect
to handle some cleanup, you can return a callback function to be invoked on the next render. One example use case would be to return cleanup function in order to cancel the timer on setTimeout
. On the first render, the setTimeout
function would run as normal, then on the second render, it will get canceled. Pretty neat stuff!
useEffect
can take a second argument of three things: nothing, an empty array, and an array with elements. It’s more than likely that you’ll either use a second arg of an array — filled or not. The second argument will run at the initial render. If there is filled array as a second arg, useEffect
will run again if the data has since been changed.
In the gist below, state is initially set to an empty array on line 3 with setState
. On line 5, we use useEffect
to fetch natively by adding an async/await
function. You can of course use other libraries like axios to handle fetching. One thing to note is that the useEffect
function itself cannot be async
, which is why, below, the async
keyword is used in data()
instead. As a second argument, we pass in the array with the results
which holds the data/state we’ve fetched and set with setResults()
.
One of the cool things functional components can do is hold multiple useState
and useEffect
functions. Depending on those three scenarios listed above, you can use as many hooks as needed.
Hooks are a more modern way to write React and offer so much more functionality to functional components. They are definitely worth learning and can really transform and optimize your React apps.