In React, state is an object that represents the components’s data and can change over time. State is used to store and manage dynamic data in a component, and it is an essential part of building interactive applications with React.
The useState
Hook is a built-in Hook in React that allows you to add state to functional components. It returns an array with two elements: the current state value and a function to update the state.
import { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
In the example above, we use the useState
Hook to add state to the MyComponent
component. The useState
Hook returns an array with two elements: the current state value (count
) and a function to update the state (setCount
). The initial state value is 0
.
The useEffect
Hook is a built-in Hook in React that allows you to perform side effects in functional components. It is similar to componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle methods in class-based components.
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
In the example above, we use the useEffect
Hook to update the document title every time the count
state changes. The useEffect
Hook takes a function as an argument and this function is executed after the component is rendered.
Conditional rendering with the useEffect Hook
You can use the useEffect
Hook to perform conditional rendering in functional components. For example, you can use the useEffect
Hook to fetch data from an API and render the data only when it is available.
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []);
if (!data) {
return <p>Loading...</p>;
}
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
In the example above, we use the useEffect
Hook to fetch data from an API and store the data in the data
state. The useEffect
Hook is executed only once (when the component is mounted) because we passed an empty array as the second argument. If the data
state is null
, we render a “Loading…” message. If the data
state is not null
, we render a list of items.
Managing side effects with the useEffect Hook
The useEffect
Hook allows you to manage side effects in your components. For example, you can use the useEffect
Hook to subscribe to a data source and update the component’s state when the data source changes.
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const subscription = dataSource.subscribe(data => setData(data));
return () => {
subscription.unsubscribe();
};
}, []);
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
In the example above, we use the useEffect
Hook to subscribe to a data source and update the data
state when the data source changes. The useEffect
Hook returns a function that is executed when the component is unmounted, and we use this function to unsubscribe from the data source.
Cleaning up effects with the useEffect Hook
The useEffect
Hook allows you to clean up effects before the component is unmounted. For example, you can use the useEffect
Hook to cancel a network request when the component is unmounted.
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
if (!cancelled) {
setData(data);
}
};
fetchData();
return () => {
cancelled = true;
};
}, []);
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
In the example above, we use the useEffect
Hook to fetch data from an API and store the data in the data
state. We also use a flag (cancelled
) to cancel the network request when the component is unmounted. The useEffect
Hook returns a function that is executed when the component is unmounted, and we use this function to set the cancelled
flag to true
.
Custom Hooks
Custom Hooks are a way to reuse stateful logic across components. You can create a Custom Hook by writing a function that starts with the use
prefix and contains a call to one or more Hooks.
import { useState, useEffect } from 'react';
function useData() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
};
fetchData();
}, []);
return data;
}
function MyComponent() {
const data = useData();
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
In the example above, we create a Custom Hook called useData
that fetches data from an API and stores the data in the data
state. We use the useData
Hook in the MyComponent
component to retrieve the data and render it in a list.
Custom Hooks are a powerful tool to abstract stateful logic and reuse it across components. You can create Custom Hooks for any type of stateful logic, such as form validation, pagination, and more.
Conclusion
In this article, we learned about the useState
and useEffect
Hooks in React. The useState
Hook allows you to add state to functional components, and the useEffect
Hook allows you to manage side effects in your components. We also learned about Custom Hooks, which are a way to reuse stateful logic across components.
Now that you have a solid understanding of the useState
and useEffect
Hooks, you can start using them in your own React projects. Remember to always follow the rules of Hooks, and don’t forget to check out the React documentation for more information on Hooks.
Exercises
To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.
Write a custom Hook that fetches data from an API and stores the data in the component’s state.
import { useState, useEffect } from 'react';
function useData(url) {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(url);
const data = await response.json();
setData(data);
};
fetchData();
}, []);
return data;
}
Write a functional component that uses the custom Hook from exercise 1 to fetch data from an API and render the data in a list.
import { useData } from './useData';
function MyComponent() {
const data = useData('https://api.example.com/data');
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Write a functional component that uses the useEffect
Hook to subscribe to a data source and update the component’s state when the data source changes.
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const subscription = dataSource.subscribe(data => setData(data));
return () => {
subscription.unsubscribe();
};
}, []);
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Write a functional component that uses the useEffect
Hook to cancel a network request when the component is unmounted.
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
if (!cancelled) {
setData(data);
}
};
fetchData();
return () => {
cancelled = true;
};
}, []);
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Write a custom Hook that manages form validation for a login form. The custom Hook should include functions for validating the email and password fields, and for submitting the form.
import { useState, useEffect } from 'react';
function useFormValidation(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
useEffect(() => {
const validateEmail = email => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
return emailRegex.test(email) ? null : 'Invalid email';
};
const validatePassword = password => {
return password.length >= 6 ? null : 'Password must be at least 6 characters';
};
const validateForm = () => {
const newErrors = {};
if (!values.email) {
newErrors.email = 'Email is required';
} else {
newErrors.email = validateEmail(values.email);
}
if (!values.password) {
newErrors.password = 'Password is required';
} else {
newErrors.password = validatePassword(values.password);
}
setErrors(newErrors);
return Object.values(newErrors).every(error => error === null);
};
return validateForm;
}, [values]);
const handleChange = event => {
const { name, value } = event.target;
setValues({ ...values, [name]: value });
};
const handleSubmit = event => {
event.preventDefault();
validateForm();
};
return {
values,
errors,
handleChange,
handleSubmit,
};
}