I started using React Hooks for my new projects and I love the simplicity of code and increased productivity. In this post I'll give you a high level overview of Hooks and how you can start using them now without having to learn everything there is to know about them.
Hooks allow us to use state and other React features in function based components, you no longer need to write class based components. It's not that there's anything wrong with classes, but Hooks will most likely make your code much simpler and improve reusability.
React provides some built-in Hooks and you can also create your own.
The useState
Hook can be called inside a function component to add some local state to it. React will preserve this state between renders. useState
returns an array with two items:
The only argument to useState
is the initial state.
One important difference between this.setState
in class components is that with Hooks updating a state variable always replaces it instead of merging it.
Here's an example of a simple search input:
import React, { useState } from 'react';
const Search = () => {
const [searching, setSearching] = useState(false);
const [keywords, setKeywords] = useState('');
return (
<div className="search">
<input type="search" value={keywords} onChange={e => setKeywords(e.target.value)} />
<button onClick={() => setSearching(true)}>Search</button>
</div>
);
};
export default Search;
We're using the useState
hook twice, to set the keywords
to state as the user types in, and set searching
boolean to true once the user clicks the search button.
But how do we perform an actual search?
The useEffect
Hook adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount
, componentDidUpdate
and componentWillUnmount
in class based components, but unified into a single API.
By default, React runs the effects after every render, including the initial one. React guarantees the DOM has been updated by the time it runs the effects.
For our search example, we can use the useEffect
Hook to perform the search, but we need to check whether the user clicked the search button before searching, otherwise the search would be performed every time the component rerenders (in this case every time the user types in something into the search input).
import React, { useState, useEffect } from 'react';
const Search = () => {
const [searching, setSearching] = useState(false);
const [keywords, setKeywords] = useState('');
useEffect(() => {
if (searching && keywords != '') {
console.log('Search for keywords: ', keywords);
// this is where we would call an API to perform the search
return () => {
setSearching(false);
};
}
});
return (
<div className="search">
<input type="search" value={keywords} onChange={e => setKeywords(e.target.value)} />
<button onClick={() => setSearching(true)}>Search</button>
</div>
);
};
export default Search;
useEffect
has a nice feature to perform a cleanup after running an effect. If an effect returns a function, React will run it when it's time to clean up. In our search example, we use it to set searching
back to false
.
React allows us to skip an effect unless a prop changes. We don't need this feature on our search example, but it would work something like this:
useEffect(() => {
// call some API
}, [props.id]); // run the effect only if the id changes
Passing an empty array []
of inputs tells React that your effect doesn't depend on any values from the component, so it would run only on mount and clean up on unmount, it wound't run on updates.
Note: You can include multiple useEffect
functions in a component and React will run each one, in the order they were specified.
The useLayoutEffect
Hook is identical to useEffect
, but it fires synchronously after all DOM mutations. You can use it to to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect
will be flushed synchronously, before the browser has a chance to paint.
Basically, useEffect
fires after layout and paint, during a deferred event and doesn't block the browser from updating the screen, while useLayoutEffect
fires before and you should use it only if you need to ensure that DOM mutations fire synchronously before the next paint so that the user does not perceive a visual inconsistency.
The useContext
Hook lets us subscribe to React context without introducing nesting:
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
}
useContext
accepts a context object (the value returned from React.createContext
) and returns the current context value, as given by the nearest context provider for the given context.
When the provider updates, the useContext
Hook will trigger a rerender with the latest context value.
The useReducer
Hook lets us manage local state of complex components with a reducer:
function Example() {
const [todos, dispatch] = useReducer(todosReducer);
}
It's an alternative to useState
.
useReducer
accepts a reducer of type (state, action) => newState
and returns the current state paired with a dispatch
method.
useReducer
is usually preferable to useState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. It also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
The useCallback
Hook returns a memoized callback:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
You pass an inline callback and an array of inputs. useCallback
will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
The useMemo
Hook returns a memoized value. You pass a create function and an array of inputs.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
The useRef
Hook returns a mutable ref object whose .current
property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
const refContainer = useRef(initialValue);
A common use case is to access a child imperatively. useRef
is useful for more than the ref
attribute, it is handy for keeping any mutable value around similar to how you'd use instance fields in classes.
The useImperativeHandle
Hook customizes the instance value that is exposed to parent components when using ref
.
useImperativeHandle(ref, createHandle, [inputs]);
The useDebugValue
Hook can be used to display a label for custom hooks in React DevTools:
useDebugValue(value);
It's not recommended to add debug values to every custom Hook. It's most valuable for custom Hooks that are part of shared libraries.
There are two important rules with Hooks:
By creating custom Hooks you can extract component logic into reusable functions.
Custom Hooks are more of a convention than a feature. If a function's name starts with use
and it calls other Hooks, React will know that it's a custom Hook.
Let's refactor our original search example to remove the search button and instead search when the visitor presses the *Enter key on their keyboard.
First let's create a custom useKeyPress
hook that will check which keyboard key the visitor pressed:
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
function downHandler({ key }) {
if (key === targetKey) {
setKeyPressed(true);
}
}
const upHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, []);
return keyPressed;
}
Our custom hook accepts a single parameter, the key that we're expecting to be pressed. We created two event handler functions and added event listeners inside the useEffect
Hook. Notice that we passed an empty array []
as the second argument to useEffect
to ensure it only runs on mount and unmount. We're also removing event listeners on cleanup.
Now we can use the useKeyPress
Hook in our Search
component:
const Search = () => {
const [keywords, setKeywords] = useState('');
let pressedEnter = useKeyPress('Enter');
useEffect(() => {
if (pressedEnter && keywords != '') {
console.log('Search for keywords: ', keywords);
// this is where we call an api to perform the search
}
}, [pressedEnter]);
return (
<div className="search">
<input type="search" value={keywords} onChange={e => setKeywords(e.target.value)} />
</div>
);
};
export default Search;
We removed the search button and searching
state variable and only checking whether the visitor pressed Enter. We also added pressedEnter
to the array of inputs to skip the effect unless the pressedEnter variable changes.
I hope this was helpful to get you started with React Hooks. Next check out the official Hooks documentation and other Hooks tutorials.