The original link
React Hooks were introduced in the React Conf in October 2018 as a way to use state and side effects in the React function component. Although functional components were formerly known as function stateless components (FSC), they were eventually able to use state with React Hooks. As a result, many people now refer to them as function components.
In this walkthrough, I want to explain the motivation behind hook functions and what happens to React. This tutorial is just an introduction to React Hooks. At the end of this tutorial, you’ll find more tutorials to delve into React Hooks. Why use React Hooks? React Hooks were invented by the React team to introduce state management and side effects into function components. This is their way of making the React function component easier by using side effects or states instead of using life cycle methods to refactor the React function component into a React class component. The React Hooks allow us to write React applications using only function components.
Unnecessary component refactoring: Previously, only React class components were used for local state management and lifecycle methods. The latter is critical to introducing side effects such as listeners or data retrieval in React class components.
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 }) } > Click me </button> </div> ); }}export default Counter;
Copy the code
Use the React stateless component only when you do not need state or lifecycle methods. And because the React function component is lighter (and prettier), people already use a lot of it. The downside of this is that the component is refactored from the React function component to the React class component every time a state or lifecycle method is needed (and vice versa).
import React, { useState } from 'react';
// how to use the state hook in a React function component
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
Copy the code
This refactoring is not required when Hooks are used. The side effects and states are finally available in the React function component. This is a reasonable reason to rename function stateless components to function components.
Side effects logic: In React class components, side effects are mostly introduced in life cycle methods (e.g. ComponentDidMount, componentDidUpdate, componentWillUnmount). Side effects can be getting data in React or interacting with the Browser API. Often, these side effects come from the setup and cleanup phase. For example, if you want to remove your listeners, you may encounter React performance issues.
// side-effects in a React class component
class MyComponent extends Component {
// setup phase
componentDidMount() {
// add listener for feature 1
// add listener for feature 2
}
// clean up phase
componentWillUnmount() {
// remove listener for feature 1
// remove listener for feature 2
}
...
}
// side-effects in React function component with React Hooks
function MyComponent() {
useEffect(() => {
// add listener for feature 1 (setup)
// return function to remove listener for feature 1 (clean up)
});
useEffect(() => {
// add listener for feature 2 (setup)
// return function to remove listener forfeature 2 (clean up) }); . }Copy the code
Now, if you were to introduce more than one of these side effects in the lifecycle methods of the React class component, all side effects would be grouped by lifecycle methods, not by side effects themselves. This is where React Hooks encapsulate side effects with the useEffect hook function, and each hook has its own side effects during the processing and cleanup phases. You’ll see how to do this by adding and removing listeners in React Hook later in this tutorial.
React abstraction hell: The high-order components and render props components in React represent abstraction as well as reusability. There is also the React Context and its Provider and Consumer components, which introduce another level of abstraction. All of these advanced modes in React use so-called wrapper components. The implementation of the following components should be familiar to developers building larger React applications.
import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';
function App({ history, state, dispatch }) {
return (
<ThemeContext.Consumer>
{theme =>
<Content theme={theme}>
...
</Content>
}
</ThemeContext.Consumer>
);
}
export default compose(
withRouter,
withReducer(reducer, initialState)
)(App);
Copy the code
Sophie Alpert called it “packaging hell” in React. You will see it not only in the implementation, but also when you examine components in the browser. There are dozens of wrapped components, thanks to the Render Prop component (including the Consumer component in the React context) and the high-order component. It becomes an unreadable component tree because all the abstract logic is obscured by the other React components. The components that are actually visible are hard to find in the browser DOM. So what if you don’t need these add-ons, but just encapsulate the logic as a side effect in functions? Then you can remove all these wrapping components and flatten the structure of the component tree:
function App() {
const theme = useTheme();
const history = useRouter();
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Content theme={theme}>
...
</Content>
);
}
export default App;
Copy the code
This is what React Hooks proposed. All side effects exist directly in the component, without introducing other components as containers for business logic. The container is gone, and the logic exists only in the React Hooks of the function.
JavaScript Class chaos: JavaScript is a good blend of two concepts: object-oriented programming (OOP) and functional programming. React shows developers both of these concepts well. On the one hand, React (and Redux) introduced functional programming (FP) consisting of functions, as well as general programming concepts for other functions (e.g., higher-order functions, JavaScript built-in methods (e.g., Map, reduce, filter), and other terms, Such as immutability and side-effects. React itself doesn’t really introduce these things, because they’re a function of the language or programming paradigm itself, but they’re used so extensively in React that every React developer subtly becomes a better JavaScript developer.
React, on the other hand, uses JavaScript classes as a way to define the React component. Classes are just declarations, and components are actually instantiations of classes. It creates an instance of a class whose this object is used to interact with class methods (for example, setState, forceUpdate, other custom class methods). But for React beginners without object-oriented programming (OOP), the learning curve is steeper. This is why class binding, this object, and class inheritance can be confusing.
Now, many people think React should not cancel JavaScript classes. This is because people don’t understand React Hooks. However, one of the assumptions introduced into the Hooks API is that the learning curve for React beginners is smoother when they start writing the React component without JavaScript classes.
React Hooks: What changes have happened to React?
Every time a new feature is introduced, people pay attention to it. It excites innovators, and terrifies others. The question I hear people most concerned about is:
- Everything has changed! It’s horrible to think about…
- React becomes bloated like Angular!
- This doesn’t work. You can use the Class component.
Let me answer these questions:
Everything has changed: React Hooks will change the way we write React applications in the future. But nothing has changed. You can still write class components using local state and lifecycle methods, as well as other components such as higher-order components or render props. The React team ensures React remains backward compatible. Same as React 16.7.
React has become as bloated as Angular: React has long been seen as a library with a lightweight API. Yes, and in the future. However, in order to adapt to the practice of building component-based applications a few years ago and not be replaced by other libraries, React introduced some changes to support older apis. If React had something completely new, it would only have function components and React Hooks. But React was released a few years ago and needed to be tweaked to keep up or change the status quo. The React class components and lifecycle methods will probably be deprecated in a few years in favor of the React function components and React Hooks, but for now, the React team is keeping the React class components in its toolkit. After all, the React team used hooks as an invention that they wanted to use for a long time. React Hooks obviously add another API to React, but help simplify the React API in the future. I like the transition, rather than a radically different Version of React2.
This doesn’t work, and it works fine with the Class component: assume you learn React from scratch, and then introduce you directly to React Hooks. Perhaps creating a React application would not start with a React class component, but with a React function component. All the components you need to learn are React Hooks. They manage state and side effects, so you only need to know about state and Effect hooks. For React beginners, there is no need to use JavaScript classes (inheritance, this, binding, super,…) All the other overhead that the React class component did before. Learn React from React Hooks. Imagine that React Hooks are a new way of writing React components – it’s a new way of thinking. I have attended many React seminars and AM a skeptical person, but when I wrote some simple scenarios with React Hooks, I was convinced that this was the easiest way to write and learn React.
Finally, think of it this way: component-based solutions (such as Angular, Vue, and React) push the boundaries of Web development with every release. They build on technologies invented more than two decades ago and are constantly being tweaked to make Web development a breeze in 2018. They have frantically optimized it to meet contemporary needs. We are building Web applications using components instead of HTML templates. I can imagine the future as we will sit together and invent component-based standards for browsers. Angular, Vue, and React are just the pioneers of this movement.
React UseState Hooks
You’ve seen a typical counter example of useState Hook in the code. It is used to manage local state in functional components. Let’s use the hook function in a more complex scenario, in which we will manage list items:
import React, { useState } from 'react';
const INITIAL_LIST = [
{
id: '0',
title: 'React with RxJS for State Management Tutorial',
url:
'https://www.robinwieruch.de/react-rxjs-state-management-tutorial/',
},
{
id: '1',
title: 'React with Apollo and GraphQL Tutorial',
url: 'https://www.robinwieruch.de/react-graphql-apollo-tutorial',},];function App() {
const [list, setList] = useState(INITIAL_LIST);
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
Copy the code
The useState hook function takes an initial state as an argument and returns two namable variables by using array destruction. The first variable is the value of state, and the second variable is the function that updates the state.
The goal of this example is to remove an item from the list. To achieve this, each item in the render list has a button with a click event. You can inline an onRemoveItem function in the function component, which will use list and setList later. There is no need to pass these variables to the function because they are already available outside the scope of the component.
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem(id) {
// remove item from "list"
// set the new list in state with "setList"
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
Copy the code
We need to somehow know which list items should be removed from the list. Using higher-order functions, we can pass the id of the item to the handler. Otherwise, we will not be able to identify items that should be removed from the list.
function App() {
const [list, setList] = useState(INITIAL_LIST);
functiononRemoveItem(id) { const newList = list.filter(item => item.id ! == id); // Delete the target itemsetList(newList); // Reset the list}return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
Copy the code
You can remove list items from the list based on the ID passed to the function. The filter function is then used to filter the list and the setList function is used to set the new state of the list.
The useState hook function gives you everything you need to manage state in functional components: initial state, latest state, and state update functions. Everything else is JavaScript. In addition, you don’t have to worry as much about shallow merging of state objects as you did with class components. Instead, you can use useState to encapsulate a field (for example, a list), but if you need another state (for example, a counter), just use another useState to encapsulate that field.
React UseEffect Hooks
Let’s move on to the next hook function called useEffect. As mentioned above, functional components should be able to manage state and side effects through hooks. The administrative state is displayed through the useState hook. Now comes the useEffect hook for generating side effects that are typically used to interact with the Browser/DOM API or external apis such as data extraction. Let’s see how the useEffect hook function can interact with the browser API by implementing a simple stopwatch:
import React, { useState } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
return( <div> {! isOn && ( <buttontype="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
Copy the code
There is no stopwatch yet. But at least one conditional render displays a “start” or “stop” button. The state of the button is managed by the useState hook.
Let’s introduce our side effect with useEffect, which records an interval. It issues a console.log every second, which is printed on the console.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
setInterval(() => console.log('tick'), 1000); // execute once per second});return( <div> {! isOn && ( <buttontype="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
Copy the code
To empty the timer when a component is unloaded, you can return a function in useEffect for any cleanup operation. For example, there should be no memory leaks when the component no longer exists.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return() => clearInterval(interval); // This will empty the timer when the component is uninstalled}); . }export default App;
Copy the code
Now you set the side effects when you mount the component and clean them up when you unload the component. If you keep track of how many times the function in the useEffect hook is called, you’ll see that it sets a new interval each time the component state changes (for example, click the Start/Stop button).
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
console.log('effect runs');
const interval = setInterval(() => console.log('tick'), 1000);
return() => clearInterval(interval); }); . }export default App;
Copy the code
To perform side effects only when the component is mounted and unmounted, you can pass it an empty array as the second argument.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return() => clearInterval(interval); } []); // Thus useEffect is executed only during mount and unmount phases... }export default App;
Copy the code
However, since the timer is cleared with each uninstall, we also need to setInterval in the update cycle. However, we can tell the useEffect to run only when the isOn variable changes. UseEffect runs during the update cycle only if one of the variables in the array changes. If you leave the array empty, the effect will only run at mount and unload time, because there are no variables available to run the side effects again.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return() => clearInterval(interval); }, [isOn]); // useEffect is executed when isOn changes... }export default App;
Copy the code
SetInterval will run whether isOn is true or false. But we only want to run it when the stopwatch is active:
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(() => console.log('tick'), 1000);
}
return() => clearInterval(interval); }, [isOn]); . }export default App;
Copy the code
Now introduce another state in the functional component to track the stopwatch timer. It is used to update the timer, but only when the stopwatch is activated.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(Timer +1), // When the stopwatch is activated, the Timer is +1 1000,); }return () => clearInterval(interval);
}, [isOn]);
return( <div> {timer} {! isOn && ( <buttontype="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
Copy the code
There is still an error in the code. When setInterval runs, it increments by one per second, updating the timer. However, it always depends on the expired state of the timer. The state is normal only when inOn changes. In order to always receive the latest state of the timer when the interval runs, you can use functionality for a status update feature that always has the latest state.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return() => clearInterval(interval); }, [isOn]); . }export default App;
Copy the code
Another option is to run the effect when the timer changes. The effect will then receive the latest timer state.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return() => clearInterval(interval); }, [isOn, timer]); . }export default App;
Copy the code
This is an implementation of a stopwatch using the browser API. If you continue, you can also extend the example by providing a “reset” button.
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
const onReset = () => {
setIsOn(false);
setTimer(0);
};
return( <div> {timer} {! isOn && ( <buttontype="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
<button type="button" disabled={timer === 0} onClick={onReset}>
Reset
</button>
</div>
);
}
export default App;
Copy the code
The useEffect hook function is used for side effects in the React function component, which is used to interact with browser/DOM APIS or other third-party apis (for example, data retrieval).
Rect custom hook function
Finally, after you’ve looked at the two most popular hook functions that introduce states and side effects into functional components, I’ll show you one last thing: custom hooks. That’s right, you can implement your own custom React Hook that can be reused in your application or other applications. Let’s see how they work with a sample application that detects whether your device is online or offline.
import React, { useState } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
if (isOffline) {
return<div>Sorry, you are offline ... </div>; }return<div>You are online! </div>; }export default App;
Copy the code
Again, the useEffect hook is introduced to produce side effects. In this case, the effect adds and removes listeners to check whether the device is online or offline. The two listeners are only set once at installation and cleared once at uninstallation (the empty array is the second argument). Each time one of the listeners is called, it sets the state for the isOffline Boolean value.
import React, { useState, useEffect } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline); }; } []);if (isOffline) {
return<div>Sorry, you are offline ... </div>; }return<div>You are online! </div>; }export default App;
Copy the code
Everything is now nicely encapsulated in a useEffect that can be reused elsewhere. This is why we can extract this functionality into its custom hook function, which follows the same naming convention as the other hooks.
import React, { useState, useEffect } from 'react';
function useOffline() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline); }; } []);return isOffline;
}
function App() {
const isOffline = useOffline();
if (isOffline) {
return<div>Sorry, you are offline ... </div>; }return<div>You are online! </div>; }export default App;
Copy the code
Extracting custom hooks as functions is not the only thing. You must also return state from the custom hook function according to isOffline in order to use it in your application to display messages to offline users. This is a custom hook that detects whether you are online or offline. You can read more about custom hooks in the React documentation.
React Hooks are very reusable because it is possible to develop a custom React Hooks ecosystem that can be installed from NPM into any React application.