Lifecycle Methods and useEffect in React

The useEffect hook in React allows you to perform side effects in function components. It's a close equivalent to lifecycle methods in class components, like componentDidMount, componentDidUpdate, and componentWillUnmount.

1. How useEffect Renders

First, import useEffect from React and create a useEffect function.

import { useEffect } from 'react';

useEffect(() => {
    // This will run once when the component mounts
    return () => {
        // This will run when the component unmounts
    }
}, []);

Single Render on Page Load

When the dependency array ([]) is provided, the effect will run once when the component mounts. This is useful for tasks such as initializing data or setting up subscriptions that only need to happen once.

useEffect(() => {
    // This will run once when the component mounts
    return () => {
        // This will run when the component unmounts
    }
}, []);
Pitfall

If you forget to include the dependency array ([]), the effect will run on every render, which can lead to performance issues and unintended behavior.

Render on Every Value Change

When no dependency array is provided, the effect will run after every render. This is useful for tasks that need to be executed on every render, regardless of the component's state or props.

useEffect(() => {
    // This will run every time the component updates
    return () => {
        // Cleanup function
    }
});
Pitfall

Omitting the dependency array will cause the effect to run after every render, which may lead to performance issues if not handled properly.

Render When a Specific Value Changes

When a dependency array with specific values is provided, the effect will only run when one of those values changes. This is useful for tasks that depend on specific state or props values.

useEffect(() => {
    // This will run when the 'count' variable changes
    return () => {
        // Cleanup function
    }
}, [count]);
Pitfall

Including too many dependencies in the dependency array can cause the effect to run more often than necessary, which can impact performance.

2. How useEffect Works

When useEffect is called, it first executes the effect function and then returns the cleanup function.

useEffect(() => {
    console.log('outside');
    return () => {
        console.log('inside');
    }
}, [count]);
Danger

Returning a cleanup function is crucial when dealing with subscriptions or event listeners to prevent memory leaks and unwanted side effects.

Example: Fetching Data with useEffect

Here's an example of how to use useEffect to fetch data from an API.

import React, { useState, useEffect } from 'react';

const FetchDataExample = () => {
    const [resource, setResource] = useState('posts');
    const [data, setData] = useState([]);

    useEffect(() => {
        fetch(`https://jsonplaceholder.typicode.com/${resource}`)
            .then(response => response.json())
            .then(json => setData(json));
        console.log(data);
    }, [resource]);

    return (
        <>
            <h1>{resource}</h1>
            <button onClick={() => setResource('users')}>Users</button><br /><br />
            <button onClick={() => setResource('comments')}>Comments</button><br /><br />
            <button onClick={() => setResource('posts')}>Posts</button>
            <div className="">
                {data.map((val, index) => (
                    <p key={index}>{val.title}{val.name}</p>
                ))}
            </div>
        </>
    );
};

export default FetchDataExample;
Danger

Be cautious with the dependencies of useEffect when dealing with asynchronous operations. Failing to include dependencies like state setters can cause stale closures and bugs.

Example: Cleaning Up Side Effects

When defining a function that runs outside of the return, it may slow down the app. We use cleanup functions to clear these effects.

import React, { useState, useEffect } from 'react';

const ResizeExample = () => {
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
        const handleResize = () => {
            setWidth(window.innerWidth);
        };

        window.addEventListener('resize', handleResize);

        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, [width]);

    return (
        <>
            <h1>{width}</h1>
            <button onClick={() => setWidth(window.innerWidth)}>{width}</button>
        </>
    );
};

export default ResizeExample;
Pitfall

Be mindful of the dependency array to avoid running the cleanup function and effect unnecessarily.

Task

Practice Using useEffect

Objective: Create a component that fetches data from an API and updates the component based on the data.

  1. Create a FetchData Component:

    Create a new file FetchData.jsx and define a component that fetches data from an API.

    // FetchData.jsx
    import React, { useState, useEffect } from 'react';
    
    const FetchData = () => {
        const [data, setData] = useState([]);
    
        useEffect(() => {
            fetch('https://jsonplaceholder.typicode.com/posts')
                .then(response => response.json())
                .then(json => setData(json));
        }, []);
    
        return (
            <div>
                {data.map((post) => (
                    <p key={post.id}>{post.title}</p>
                ))}
            </div>
        );
    };
    
    export default FetchData;
    
  2. Create a Resize Component:

    Create a new file Resize.jsx and define a component that updates the window width on resize.

    // Resize.jsx
    import React, { useState, useEffect } from 'react';
    
    const Resize = () => {
        const [width, setWidth] = useState(window.innerWidth);
    
        useEffect(() => {
            const handleResize = () => {
                setWidth(window.innerWidth);
            };
    
            window.addEventListener('resize', handleResize);
    
            return () => {
                window.removeEventListener('resize', handleResize);
            };
        }, []);
    
        return (
            <div>
                <h1>{width}</h1>
            </div>
        );
    };
    
    export default Resize;
    
  3. Combine Components in App:

    Import and use both FetchData and Resize components in your App component to see them in action.

    // App.jsx
    import React from 'react';
    import FetchData from './FetchData';
    import Resize from './Resize';
    
    const App = () => {
        return (
            <>
                <FetchData />
                <Resize />
            </>
        );
    };
    
    export default App;
    

This task will help you practice using the useEffect hook to manage side effects such as data fetching and window resize events in React components.