React Hooks
# React hooks
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class component.
After this addition, functional component is no longer a stateless component. Now, we can achieve the component behavior using function the way we use to have with class component lifecycle hooks like ComponentDidMount, ComponentWillUnmount, componentDidUpdate etc.. Those where the commonly used important component and we can achieve the same with useEffect hook.
We’ve often had to maintain components that started out simple but grew into an unmanageable mess of stateful logic and side effects. Each lifecycle method often contains a mix of unrelated logic. For example, components might perform some data fetching in componentDidMount and componentDidUpdate. However, the same componentDidMount method might also contain some unrelated logic that sets up event listeners, with cleanup performed in componentWillUnmount. Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method. This makes it too easy to introduce bugs and inconsistencies.
In many cases it’s not possible to break these components into smaller ones because the stateful logic is all over the place. It’s also difficult to test them. This is one of the reasons many people prefer to combine React with a separate state management library. However, that often introduces too much abstraction, requires you to jump between different files, and makes reusing components more difficult.
To solve this, Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods. You may also opt into managing the component’s local state with a reducer to make it more predictable. Below are lists of the hooks that got added -
Basic Hooks
- useState
- useEffect
- useContext
Additional Hooks
- useCallback
- useMemo
- useReducer
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
useState
Returns a stateful value, and a function to update it. If the new state is computed using the previous state, you can pass a function to setState or a updated value. In case of function - it will receive the previous value, and return an updated value. Here’s an example of a counter component that uses both forms of setState:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
Let's convert class component to functional component to submit firstName an lastName in a form. Below is UserForm class Component.
import React, { Component } from "react";
class UserForm extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
render() {
const { firstName, lastName } = this.state;
return (
<form>
<input
value={firstName}
onChange={this.handleInputChange}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={this.handleInputChange}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
}
export default UserForm;
Equivalent functional component for the above class component can be written as below -
import React, { useState } from "react";
import "./styles.css";
function UserForm() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
return (
<form>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={e => setLastName(e.target.value)}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
export default UserForm;
We’re basically declaring a state variable and a function to allow us to modify the state variable later. The empty string in the useState call is the initial value of firstName and can be set to any required value. We’ll set it to an empty string for now.
Note that you can name the setFirstName function whatever you want. It is a convention, however, to append ‘set’ before the name of the state variable we’re modifying.
useEffect
useEffect is meant to handle any sort of "side effect" (making a change in some external system, logging to the console, making an HTTP request, etc...) that is triggered by a change in your component's data or in reaction to the component rendering. It replaces componentDidMount, componentDidUnmount, and componentDidReceiveProps, or some code that is run any time your state changes. It can be challenging to grasp the nuances of its use, but by understanding when it runs and how to control that, it can become a little bit easier to wrap your head around.
Run the effect on every render
import React, { useState, useEffect } from "react";
default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`The count is ${count}`);
});
return (
<div>
<p>Count is {count}</p>
<button
onClick={() => {
setCount(count + 1);
}}>
increase
</button>
</div>
);
}
Run the effect only once
Let's say we only wanted the effect to run a single time... think of this as a replacement for componentDidMount. To do this, pass a [] as the 2nd argument to useEffect:
import React, { useEffect } from "react";
default function Mounted() {
useEffect(() => {
console.log("mounted");
}, []);
return <div>This component has been mounted.</div>;
}
Run the effect when data changes
If what you really want is to run the effect only when a specific value changes... say to update some local storage or trigger an HTTP request, you'll want to pass those values you are watching for changes as the 2nd argument. This example will write the user's name to local storage after every time it is updated (triggered by the onChange of the input).
import React, { useState, useEffect } from "react";
default function Listen() {
const [name, setName] = useState("");
useEffect(
() => {
localStorage.setItem("name", name);
},
[name]
);
return (
<div>
<input
type="text"
onChange={e => {
setName(e.target.value);
}}
value={name}
/>
</div>
);
}
Cleaning up from your effect
Sometimes you need to undo what you've done... to clean up after yourself when the component is to be unmounted. To accomplish this you can return a function from the function passed to useEffect... that's a mouthful but let's see a real example, of what would be both componentDidMount and componentDidUnmount combined into a single effect.
import React, { useEffect } from "react";
default function Listen() {
useEffect(() => {
const listener = () => {
console.log("I have been resized");
};
window.addEventListener("resize", listener);
return () => {
window.removeEventListener("resize", listener);
};
}, []);
return <div>resize me</div>;
}
Avoid setting state on unmounted components
Because effects run after the component has finished rendering, and because they often contain asynchronous code, it's possible that by the time the asynchronous code resolves, the component is no longer even mounted! When it gets around to calling the setData function to update the state, you'll receive an error that you can't update state on an unmounted component.
The way we can solve the stated (no pun intended) issue above, is by using a local variable and taking advantage of the "cleanup" function returned from our effect function. By starting it off as true, we can toggle it to false when the effect is cleaned up, and use this variable to determine whether we still want to call the setData function or not.
import React, { useState, useEffect } from "react";
import Axios from "axios";
default function Fetcher({ url }) {
const [data, setData] = useState(null);
useEffect(
() => {
// Start it off by assuming the component is still mounted
let mounted = true;
const loadData = async () => {
const response = await Axios.get(url);
// We have a response, but let's first check if component is still mounted
if (mounted) {
setData(response.data);
}
};
loadData();
return () => {
// When cleanup is called, toggle the mounted variable to false
mounted = false;
};
},
[url]
);
if (!data) {
return <div>Loading data from {url}</div>;
}
return <div>{JSON.stringify(data)}</div>;
}
useContext
useContext makes Context a little easier to consume. The typical way to use the Context API looks like this:
import React from "react";
import ReactDOM from "react-dom";
// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }
function App() {
// Use the Provider to make a value available to all
// children and grandchildren
return (
<NumberContext.Provider value={42}>
<div>
<Display />
</div>
</NumberContext.Provider>
);
}
function Display() {
// Use the Consumer to grab the value from context
// Notice this component didn't get any props!
return (
<NumberContext.Consumer>
{value => <div>The answer is {value}.</div>}
</NumberContext.Consumer>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
See how we get the value inside the Display component? We have to wrap our content in a NumberContext.Consumer and use the render props pattern – passing a function as a child – to retrieve the value and display it.
This works fine, and “render props” is a great pattern for passing dynamic data around, but it does introduce some unnecessary nesting and some cognitive overhead (especially if you’re not used to it).
Let’s rewrite the Display using the new useContext hook and see what it looks like:
// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';
// ...
function Display() {
const value = useContext(NumberContext);
return <div>The answer is {value}.</div>;
}
useCallback vs useMemo
The APIs of useCallback and useMemo look similar. They both take in a function and an array of dependencies.
useCallback(fn, deps);
useMemo(fn, deps);
So what is the difference? useCallback returns its function uncalled so you can call it later, while useMemo calls its function and returns the result.
You want to use useCallback or useMemo whenever you depend on referential equality between renders. In React functions defined in function components get re-created on every render because of closure. And therefore if you are using function as dependency in useEffect this will lead to infinite loop. So to avoid this we should be using useCallback. Below is excellent post explaining the meaning of referential equality highlighting difference between usecallback-vs-usememo.
Hi
Thanks you sir
Great and timely article. love your explanation for the useContext hook. thanks