Back to Course

Learn React

0% Complete
0/0 Steps
Lesson 12 of 32
In Progress

Using State and Effect Hooks

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,
  };
}