If you began using the React framework prior to version 16.8, you would agree that using class components was difficult. While using class components makes it difficult to reuse stateful logic, the presence of hooks makes it very easy to share logic between components and write simpler, more readable code.
Whether you're a seasoned React developer or just getting started, I'll walk you through some essential React hooks and practical examples.
What are React Hooks?
React Hooks is a new React feature that enables the addition of state and other React features to functional components. Only class components could have state and lifestyle methods prior to the introduction of React hooks. We can now make our components more reusable, improve performance, and write more readable code thanks to React hooks. React hooks were introduced in version 16.8 and have since become an essential component of React and its users.
In this tutorial, we will look at the various types of React hooks, which are as follows:
useState
useEffect
useContext
useReducer
Benefits of React hooks
The presence of React hooks has made a developer's job easier. They provide a number of advantages that make writing clean, efficient, and maintainable code easier. Let's look at the advantages of React hooks that will convince you to start using them in your applications.
Enhanced Functionality: With React hooks, you can add new features and capabilities to your components, making it easier to build complex and dynamic user interfaces.
Improved Organization: Hooks facilitate the organization and management of state and component logic, resulting in more readable code.
Code Reusability: React hooks allow you to share stateful logic across multiple components, making code reuse easier.
Now that we've established what React hooks are, examples of React hooks, and the benefits of React hooks, let's dive into the examples of React hooks and their practical applications one by one.
The useState hook
useState
is a built-in React hook for adding state to functional components. Do you understand how variables are defined and then used in components? useState
is similar to that, except you can set and change variables.
Before you can use the useState
hook in your component, you must import it at the top of your component, where you have the import react.
import React, { useState } from 'react'
The useState
function returns an array with two elements: the current state value, name
in this case, and a function setName
to update the state value. The state value can be an integer, a boolean, a string, an object, an array, or any combination of these.
The useState syntax
The following step would be to write out the useState
syntax. The name
of the state value already has a default value. It could be empty.
const [name, setName] = useState('yam')
When you return name
in your code, your browser will display "yam" because that is the default value.
return (
<div>
<h6>{name}</h6>
</div>
)
useState example
Consider a more concrete example using useState
. In this example, we would update the name
from an array at random. I made an array of foods as well as a function that changes the name
to a random food.
const food = ['Beans', 'potatoes', 'Rice', 'Eba']
const rand = () => {
setName(food[Math.floor(Math.random() * 4)])
}
The rand
function sets the name
of food from the food array at random. We'll use an onClick
event so that when we click on the name
value in our browser, it changes to any string in the food array at random.
return (
<div>
<h6 onClick={() => rand()}>{name}</h6>
</div>
)
We can see from this example that using the useState
hook allows us to set and update a value.
The useEffect hook
useEffect
is a built-in React Hook that allows you to fetch data, update the DOM, and set timers. useEffect
has two arguments, which are as follows:
Callback function: This function contains logic for fetching data, setting timeouts, adding or removing elements from an array, and updating a state.
Dependency array: The dependency array is used to determine when to re-run the side effect. If the dependency array is empty, the side effect will only run once, but if it is not empty, it will re-run whenever the values in the dependency array change.
The useEffect syntax
Because the dependency array can be empty or not, I'll demonstrate this with examples.
We have the following syntax for an empty array:
useEffect(() => {}, [])
Because this is a useEffect
hook with no callback function, add a function that increases the count
value by one.
const [count, setCount] = useState(0)
useEffect(() => {
setCount(count + 1)
}, [])
This callback function simply increments the count
value by one. Because the dependent array is empty, the side effect will only run once and then stop. This means that the value displayed in your browser will be 1.
However, if the dependent array is not empty and is occupied by count
, the side effect will continue to run. This also occurs when no dependency array is passed.
useEffect(() => {
setCount(count + 1)
}, [count])
Now that we know what useEffect
is, let's make an API call with the useEffect
hook and render it on our browser.
useEffect example
Let us not forget to import useEffect
in the component we will make use of.
import React, { useState, useEffect } from 'react'
We also imported useState
because we will be using it in the useEffect
function to set and update a state.
const [data, setData] = useState([])
useEffect(() => {
fetch('https://api.adviceslip.com/advice')
.then((response) => response.json())
.then((data) => setData([data]))
.catch((error) => console.error(error))
}, [])
The useEffect
hook's callback function makes an API call, and the setData
function updates data
based on the API response.
As you may have noticed, we have an empty dependency array that will only render the API once. To return the contents of the API to your browser, use the code snippet below.
Because the API is set to be an array, we will map the data
value to get the API contents.
return (
<div>
{data.map((item) => {
return (
<div>
<p>{item.slip.id}</p>
<p>{item.slip.advice}</p>
</div>
)
})}
</div>
)
The useContext hook
You can use useContext
to pass a state or data globally without going through the props process. useContext
allows components to easily share state or data. For example, suppose you want to pass a state from a parent component to a child component or use a state in a component that is not the child component; how would you get the data? You don't have to declare a state or function multiple times in different components when you use useContext
. All you need to do is define the state in your context file and share it with any component that requires it. This simplifies the coding process.
How to make use of useContext
To be able to access useContext in any of your components, you need to do the following:
Create a context file
To create a context file, follow these steps:
In the
src
folder of your project, create a folder and name it contexts.In the contexts folder, create a file and name it
StoreContext.js
. Keep in mind that you can name your file whatever you want.In the StoreContext.js file, write this code snippet below
import React, { createContext } from 'react'
export const StoreContext = createContext()
const StoreContextProvider = ({ children }) => {
return (
<StoreContext.Provider
value={{
}}
>
{children}
</StoreContext.Provider>
)
}
export default StoreContextProvider
The preceding code snippet is a boilerplate of what should be in the context file before adding anything else. Stay tuned as I explain the boilerplate.
The first is the createContext
. The createContext
we're importing simply creates a new context for data sharing between components.
import React, { createContext } from 'react'
The code snippet below uses the createContext
function to create and export StoreContext
. This means that you can use the content of StoreContext
in any component of your choice.
export const StoreContext = createContext()
Before we get started with an example, let me explain what the StoreContext.Provider
does. The following code snippet creates a context provider that wraps its children and provides data to them via the context. In this case, the children are any of the components to which we want to send data.
<StoreContext.Provider value={{ count, setCount }}>
{children}
</StoreContext.Provider>
I hope we can recognize how count
and setCount
came about from the code snippet above.The value
prop is set to an object containing the count
and setCount
state values, allowing our components to access them.
Let's do one more thing before we move on to an example. Next, we'll wrap StoreContextProvider
around the App.js
component. This is critical because, even after you create a context file, your states or functions cannot be used globally in any component until your context provider is wrapped around your App.js
component. So, in your index.js
component, add the following:
import StoreContextProvider from './contexts/StoreContext'
<StoreContextProvider>
<App />
</StoreContextProvider>
useContext example
In this example, we will create a StoreContext
file and define a function that will update the state by one, then pass the state and function to any component we want.
const StoreContextProvider = ({ children }) => {
const [count, setCount] = useState(0)
const add = () => {
setCount(count + 1)
}
return (
<StoreContext.Provider value={{ count, add }}>
{children}
</StoreContext.Provider>
)
}
Let's move on to the component that requires the count
state and the add
function.
To access the data provided by the context, we must import StoreContext
into the component.
import { StoreContext } from './contexts/StoreContext'
Now that we've imported the StoreContext
, we'll use the useContext
hook to return the data that we passed into our component from the StoreContext
.
const { count, add } = useContext(StoreContext)
The code in the previous snippet utilizes destructuring assignment to retrieve the count
and add
values from the object returned by the useContext
hook.
Use the following code to display the count value:
return (
<div>
<p onClick={add}>count</p>
<h1>{count}</h1>
</div>
)
When the p
element is clicked, the onClick
event handler is called, and it is defined using the add
value extracted from the context data using the useContext
hook. As a result, every time you click count in the browser, the count
value increases by one.
The useReducer hook
useReducer
, like the useState
hook, is used for state management, but it is more useful for managing complex state logic.
The useReducer syntax
The usereducer
hook takes two parameters: a reducer and an initial state value. A reducer
is a function that takes as inputs the current state and an action and returns the new state based on the action. The initialState
value represents the state's starting value and is used to initialize the state when the component is first rendered.
const [state, dispatch] = useReducer(reducer, initialState);
As previously stated, the reducer
function accepts state
and the dispatch
function and returns a new state that has been updated by the dispatch function.
useReducer example
The useReducer
hook is demonstrated in the example below. To manage a counter state, this example uses useReducer
.
import React, { useReducer } from 'react'
const App = () => {
const initialState = { count: 0 }
const [state, dispatch] = useReducer(reducer, initialState)
function reducer(state, action) {
if (action.type === 'increment') {
return { count: state.count + 1 }
} else if (action.type === 'decrement') {
return { count: state.count - 1 }
} else {
return state
}
}
return (
<div>
<p onClick={() => dispatch({ type: 'increment' })}>Increase</p>
<p onClick={() => dispatch({ type: 'decrement' })}>Decrease</p>
<h1>{state.count}</h1>
</div>
)
}
export default App
In the preceding example, we began with an import because that is the first thing you should do when attempting to access any React hook. The next step was to was initialize the count state to zero. The reducer
function accepts two parameters: state
and action
. There is an if-statement in the function that checks the type of action to perform when the onClick
event handler is triggered, then updates the count
state via the dispatch
function. So, if the action
was increment
, the count
increases by one, but if the action
was decrement
, the count
decreases by one.
Conclusion
You learned in this tutorial that using the useState and useEffect hooks allows you to manage the state and side effects of your components in a more efficient and readable manner. UseContext to share state across your components globally, and useReducer to manage complex state logic.