The source code for this blog is available on GitHub.
Note
Top

React - useEffect Hook

Cover Image for React - useEffect Hook
Chen Han
Chen Han

Both useEffect and useState are related to managing state in React, and they both have to do with asynchronous updates.

useState is used to manage state within a component, and it provides a way to update the state asynchronously. When you call the useState hook, it returns an array with two values: the current state value, and a function that you can use to update the state value. When you call the state update function, React schedules a re-render of the component with the updated state value. This update may not happen immediately, but instead may happen asynchronously, in a batched update.

useEffect, on the other hand, is used to manage side effects within a component. A side effect is any code that interacts with the outside world, such as fetching data from an API or updating the DOM. When you call the useEffect hook, you pass it a function that contains the code for the side effect. React schedules this function to run after the component has been rendered, but before the next paint. This function may also run asynchronously, so you need to be careful to handle any async code correctly.

In summary, both hooks deal with state updates and asynchronous behavior, but useState is used for managing component state, while useEffect is used for managing side effects.

Return function at the end of useEffect?

The return function at the end of useEffect is called the "cleanup function". It is used to clean up any resources that were created by the effect.

In this specific case, the cleanup function is removing the event listener that was added to the window object during the effect. This is important because if the event listener is not removed, it will continue to listen for storage events even after the component has been unmounted, which can lead to memory leaks and other issues.

By returning the cleanup function, useEffect will automatically invoke it when the component is unmounted or when the dependencies of the effect change, allowing us to clean up any resources that were created by the effect.

here's an example of using the return function at the end of useEffect to clean up after a component is unmounted:

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

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component mounted');

    const intervalId = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    return () => {
      console.log('Component unmounted');

      clearInterval(intervalId);
    };
  }, []);

  return <div>{count}</div>;
}

In this example, useEffect is used to start an interval timer that updates the count state variable once per second. The return function at the end of useEffect is used to clear the interval when the component is unmounted, preventing any memory leaks or unnecessary work from continuing after the component is no longer needed.

In this case, the cleanup function simply calls clearInterval with the ID of the interval that was created in the effect. When the component unmounts, React will automatically call this cleanup function before removing the component from the DOM.

useHasMounted

In Next.js, the code is executed on both the server and the client side. When the code runs on the server side during server rendering, the window object does not exist, because there is no browser environment. The typeof window !== "undefined" check is used to ensure that the code is only executed on the client side, where the window object exists. If the code is running on the server side, the typeof window will return undefined, and the code inside the if statement will be skipped.

const SomeClientSideOnlyComponent = () => {
  const isRenderingOnServer = typeof window === 'undefined';
  // 🙅‍♀️ Don't do this:
  if (isRenderingOnServer) {
    return null;
  }
  return <div>Client only!</div>
};

This is a bad idea; it can lead to really wonky bugs.

The reason why some might say that using typeof window === 'undefined' to determine if a component is being rendered on the server side can cause wonky bugs is because this technique relies on the assumption that the window object is only available in the browser environment and not in the server environment.

While this assumption is generally true, there are some edge cases where it might not hold. For example, if you are using a serverless function that runs in a Node.js environment but provides a browser-like context, then the window object might actually be available even in the server environment.

Additionally, if you're using code splitting and dynamically importing a module that relies on the window object, then the check for typeof window === 'undefined' might incorrectly evaluate to true even though the component is actually being rendered on the client side.

To avoid these wonky bugs, it's generally safer to use React's built-in useEffect hook to determine whether a component is being rendered on the server or client side.

React's built-in useEffect hook is called on the client side after the component has mounted, and it's not executed on the server side during server rendering. This means that you can use useEffect to set a state value that indicates whether the component is being rendered on the client or server side. For example:

import { useState, useEffect } from "react";

export default function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);

  return hasMounted;
}

The useEffect hook is used in useHasMounted to set the initial value of hasMounted to false when the component is first mounted. This is achieved by passing an empty dependency array ([]) as the second argument to useEffect, which means the effect function is only called once, when the component mounts.

Then, within the effect function, setHasMounted is called to set the value of hasMounted to true. Since hasMounted is returned by the hook, it allows the consuming component to know whether or not it has mounted, and subsequently to conditionally render certain parts of the component tree based on this knowledge.

Probably you can use like that

import { useState, useEffect } from "react";

function MyComponent() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  if (!isClient) {
    return null; // Render nothing on the server
  }

  return <div>Hello, world!</div>;
}

In this example, useEffect is used to set the isClient state value to true after the component has mounted on the client side. Before that, the component returns null to render nothing on the server side. This way, you can avoid any "wonky bugs" that may occur when using typeof window to check whether the component is being rendered on the server or client side.

So is that the reason we can write jquery or javascript dom manipulation stuff inside useEffect?

Yes, that's correct! Since useEffect runs on the client side, it's safe to manipulate the DOM or use other client-side APIs inside the effect function. This is also why it's recommended to use useEffect for any kind of side effect that needs to happen on the client side, such as fetching data or setting up event listeners.

Unmounted the setTimeout

Clearing timeouts in the cleanup function of a useEffect hook is important because it ensures that any pending or scheduled timeouts are canceled before the component unmounts. If a timeout is not cleared before a component unmounts, it can lead to memory leaks and potential errors.

For example, if a component is unmounted before the scheduled timeout is triggered, the code inside the timeout may still try to execute, leading to unexpected behavior or errors. Additionally, if the timeout is set to perform an action that updates the state of the component, it can cause a memory leak if the component is no longer in use.

By clearing the timeouts in the cleanup function of the useEffect hook, we ensure that any pending timeouts are canceled when the component unmounts, preventing potential errors and memory leaks.

useEffect(() => {
  // clear the unnecessary session storage
  setTimeout(() => {
    deleteSessionShippingAddress();
    deleteSessionSelectedOption();
    deleteSessionTax();
  }, 10000);

  // clear the rest session storage created during the checkout process
  setTimeout(() => {
    deleteSessionOrder();
    deleteSessionCart();
  }, 50000);
}, []);

You can achieve this by returning a cleanup function from the useEffect hook. In the cleanup function, you can clear the timeouts that you've set up using the clearTimeout function.

useEffect(() => {
  // Set up the timeouts
  const timeout1 = setTimeout(() => {
    deleteSessionShippingAddress();
    deleteSessionSelectedOption();
    deleteSessionTax();
  }, 10000);

  const timeout2 = setTimeout(() => {
    deleteSessionOrder();
    deleteSessionCart();
  }, 50000);

  // Return the cleanup function
  return () => {
    // Clear the timeouts
    clearTimeout(timeout1);
    clearTimeout(timeout2);
  };
}, []);

This way, when the component unmounts, the cleanup function will be called and the timeouts will be cleared, preventing any potential errors or unexpected behavior.

setTimeout

useEffect(() => {
    const checkRoute = setInterval(() => {
      const route = window.Route
      
      if (route) {
        clearInterval(checkRoute);
        
        route.Protection.render({
          storeDomain: "hyte.com",
          subtotal: 123.12,
          currency: "USD",
          environment: route.Environment.Dev,
          status: route.Coverage.ActiveByDefault,
          merchantId: '1ca68704-0b75-4189-957f-0c3a2e20768a',
          storeName: "HYTE Store",
        });
        
        route.Protection.getQuote('1ca68704-0b75-4189-957f-0c3a2e20768a', 100, "USD", () => {
          console.log("start over the route");
        });
        
        route.Protection.on("status_change", function (event: any) {
          console.log("Toggled!", event);
        });
      }
    }, 100);
  }, []);

setInterval

The console.log("current counter:", count) statement is not showing the updated value of the count state because setCount is an asynchronous function that updates the state of count on the next render. This means that the value of count inside the setInterval callback function will always be the previous value.

import { useState, useEffect } from "react";
const addOne = (c: number) => c + 1
const UsePrevious = () => {
  let _count: number = 0
  const [count, setCount] = useState(1);
  
  useEffect(() => {
    setCount((prev) => {
      console.log("rerender")
    
      return prev + 1
    })
    
    const setSimpleInterval = setInterval(() => {
      _count = addOne(_count)
      
      
      console.log("current counter:", count)
      console.log("current _counter:", _count)
    }, 1000)
    return () => {
      clearInterval(setSimpleInterval)
    };
  }, []);
  
  
 
  
  // show both current and previous value
  return (
    <div className="container mt-6">
    </div>
  );
};

export default UsePrevious;

To see the updated value of count, you can use the useEffect hook to log the value of count every time it changes. Here's an example:

import { useState, useEffect } from "react";

const addOne = (c: number) => c + 1;

const UsePrevious = () => {
  const [count, setCount] = useState(1);

  useEffect(() => {
    const setSimpleInterval = setInterval(() => {
      setCount((prevCount) => {
        const newCount = addOne(prevCount);
        console.log("current counter:", newCount);
        return newCount;
      });
    }, 1000);

    return () => clearInterval(setSimpleInterval);
  }, []);

  return <div className="container mt-6">{count}</div>;
};

export default UsePrevious;

In this code, we use the setCount function to update the state of count inside the setInterval callback function. We pass a function to setCount that takes the previous value of count as an argument and returns the new value. This ensures that we always have the latest value of count inside the callback function.

Event banner

import { useEffect, useState } from "react";

export default function useEventBanner(startDate, endDate) {
  const [showEventBanner, setShowEventBanner] = useState(false);
  useEffect(() => {
    const createIntervalForRendering = setInterval(() => {
      let currentDate = Date.now();
      if (currentDate >= new Date(startDate).getTime()) {
        setShowEventBanner(true);
      }

      if (currentDate >= new Date(endDate).getTime()) {
        setShowEventBanner(false);
        clearInterval(createIntervalForRendering);
      }
    });
    return () => {
      clearInterval(createIntervalForRendering);
    };
  }, []);
  return showEventBanner;
}

how to use it

import React from "react";
import useEventBanner from "./useEventBanner";

function EventBanner({ startDate, endDate }) {
  const showEventBanner = useEventBanner(startDate, endDate);
  
  return (
    <>
      {showEventBanner && (
        <div className="event-banner">
          <p>Event happening now!</p>
        </div>
      )}
    </>
  );
}

export default EventBanner;

In this example, the EventBanner component takes two props: startDate and endDate. It uses the useEventBanner hook to determine whether the banner should be displayed by getting the showEventBanner value from the hook.

The showEventBanner value is then used to conditionally render the banner. If showEventBanner is true, the banner is displayed with a message that says "Event happening now!".

By using the useEventBanner hook in this way, we can easily display a banner for a specific event during a certain time period without having to manually calculate the current date and time in the component.

Reference

© 2024 WOOTHINK. All Rights Reserved.
Site MapTerms and ConditionsPrivacy PolicyCookie Policy