React - useEffect Hook
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.