Hooks 🎣 API
👉 The additional API that lets you use state and other features in React without writing a class is called Hooks.
What Are React Hooks?
React Hooks are in-built functions that allow React developers to use state and lifecycle methods inside functional components, they also work together with existing code, so they can easily be adopted into a codebase. The way Hooks were pitched to the public was that they allow developers to use state in functional components but under the hood, Hooks are much more powerful than that. They allow React Developers to enjoy the following benefits:
👉 Improved code reuse;
👉 Better code composition;
👉 Better defaults;
👉 Sharing non-visual logic with the use of custom hooks;
👉 Flexibility in moving up and down the components tree.
👉 Hooks are backwards-compatible.
With React Hooks, we have the power to use functional components for almost everything we need to do from just rendering UI to also handling state and also logic — which is pretty neat. We know that components and top-down data flow help us organize a large UI into small, independent, reusable pieces. However, we often can’t break complex components down any further because the logic is stateful and can’t be extracted to a function or another component.
These cases are very common and include animations, form handling, connecting to external data sources, and many other things we want to do from our components. When we try to solve these use cases with components alone, we usually end up with: Huge components that are hard to refactor and test. Duplicated logic between different components and lifecycle methods. Complex patterns like render props and higher-order componentHooks let us organize the logic inside a component into reusable isolated units.
Hooks apply the React philosophy (explicit data flow and composition) inside a component, rather than just between the components.
What Are Hooks? 🤔
To understand Hooks, we need to take a step back and think about code reuse. Today, there are a lot of ways to reuse logic in React apps. We can write simple functions and call them to calculate something. We can also write components (which themselves could be functions or classes). Components are more powerful, but they have to render some UI. This makes them inconvenient for sharing non-visual logic. This is how we end up with complex patterns like render props and higher-order components.
Functions seem to be a perfect mechanism for code reuse. Moving logic between functions takes the least amount of effort. However, functions can’t have local React state inside them. You can’t extract behavior like “watch window size and update the state” or “animate a value over time” from a class component without restructuring your code or introducing an abstraction like Observables. Both approaches hurt the simplicity that we like about React. Hooks solve exactly that problem. Hooks let you use React features (like state) from a function — by doing a single function call. React provides a few built-in Hooks exposing the “building blocks” of React: state, lifecycle, and context.
Since Hooks are regular JavaScript functions, you can combine built-in Hooks provided by React into your own “custom Hooks”.
👉 React Hooks enable us to write React applications with only function components. Thus, there is no need to use class components anymore.
Unnecessary Component Refactorings:
Function components with React Hooks prevent unnecessary component refactorings. Previously, only React class components were used for local state management and lifecycle methods. The latter have been essential for introducing side-effects, such as listeners or data fetching, in React class components. The following shows a React class component with state management:
import React, { Component } from 'react'
class Counter extends 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
👉 So in short React Hooks let you use state and side effects in a functional components, something what was not possible before.
Before React Hooks were introduced to the world, this would be your workflow:
👉 create a functional (stateless) component
👉 ealise that you need to manage an internal state of this component
👉 rewrite this component into a class component
👉 happily manage components state
Now with React Hooks it looks much more streamlined:
👉 create a functional component
👉 happily manage components state
React Hooks let you manage the state and react to state changes in functional components.
Now lets explore the most common state management use cases in React components.
How to manage state with React Hooks? Imagine that we have a simple React component that needs to be either collapsed or expanded.
import React from 'react'
const Collapsible = () => {
return (
<div className="isExpanded">
<h1>Item title</h1>
...
</div>
)
}
We would need to rewrite this into a class component if there were no React Hooks.
Luckily we have useState React Hook.
📌 State Hook
import React, { useState } from 'react'
const Collapsible = () => {
const [state, setState] = useState(false)
return (
<div className={state ? 'isExpanded' : null}>
<h1>Item title</h1>
...
</div>
)
}
We are importing useState
from React and then creating a default state with the value of false.
Then we have access to the state variable and can render a css class when required.
If we wanted our component to be expanded by default we would set it to true by default.
state
and setState
names are customisable names, you could call them whatever you like. color, setColor or user, setUser would do the same thing.
You get the point of sticking to a clear naming here.
state is the name of the stored variable and setState is a function that updates the value of this variable.
To change the value we could add onClick event to the h1 and toggle the value to the opposite true or false.
import React, { useState } from 'react';
const Collapsible = () => {
const [state, setState] = useState(false);
return (
<div className={state ? 'isExpanded' : null}>
<h1 onClick={() => setState(!state)}>Item title</h1>
...
</div>
)
Now we are managing the internal state of our component using the useState React Hook.
You are not limited to a single value for your state, you can store objects, arrays or even nested objects.
Here is an example of a property object being stored in the state.
const [property, setProperty] = useState({
name: 'Hotel Filadelfia',
id: 'hh526',
location: 'Gert Street',
})
How to fetch data with React Hooks?
Another React Hook that you will need to master is useEffect().
It hooks into your React component in the same way as componentDidMount, componentDidUpdate, and componentWillUnmount used to.
import React, { useState, useEffect } from 'react'
const TopGames = () => {
const [games, setGames] = useState([])
useEffect(() => {
fetch('https://bgg-json.azurewebsites.net/hot')
.then((response) => response.json())
.then((data) => setGames(data))
})
return (
<div>
{games.map((game) => (
<p>{game.title}</p>
))}
</div>
)
}
We are importing useEffect React hook and then inside of it we are:
fetching data from an external API and saving the response into a local state variable called games The problem with the above code is that it would create an infinite loop!
The component mounts, we fetch data, the state gets updated, the components updates, we fetch data…and this continues.
By default useEffect() runs on the first render and on every update.
👉 How to fix the infinite loop inside of useEffect hook ? 🤔
To make the fetch call only once, we need to supply an empty array as the second parameter.
useEffect(() => {
fetch('https://bgg-json.azurewebsites.net/hot')
.then((response) => response.json())
.then((data) => setGames(data))
}, [])
This empty array tells React to run the function inside of it only after the first render, not on update.
In another example we could include a searchTerm dependency. This would make sure that the fetch call only happens when the searchTerm variable is updated.
useEffect(() => {
fetch(`https://someurl.com/api/${searchTerm}`)
.then((response) => response.json())
.then((data) => setGames(data))
}, [searchTerm])
searchTerm
is now a dependency for this side effect and unless searchTerm is updated, the fetch call would not run.
I have mentioned mount and update, but where is the componentWillUnmount()
?
If your effect returns a function, it will be executed when it is a time for the component to be unmounted.
useEffect(() => {
// do something here first
// then console.log when unmouting
return () => {
console.log('unmounting now')
}
}, [])
This will simply console.log every-time this component gets removed from the DOM.