This content originally appeared on DEV Community and was authored by Dennis Persson
Sometimes we need to run code only once. useRunOnce is a hook that runs a function one time when a component mounts, or one time per browser session. The article explains common use cases of this hook and when not to use it.
In This Article
- useRunOnce Hook
- Run Once on Mount
- Run Once per Session
- Use Cases
- When Not To Use
- Examples
- Summary
useRunOnce Hook
Below you can see how useRunOnce hook is implemented in JavaScript and typescript. The hook can be used to run a function once, either on mount or per browser session.
The hook takes an object as an argument, with two available properties. Firstly, a required fn property that is the callback function that will run. If no other property is passed, the callback function will run once every time the component mounts.
If the second property sessionKey is passed, the hook will instead utilize session storage to run the callback function just once per browser session. That is further explained later in this article.
The code is also available at CodeSandbox and GitHub. You can try it out on CodeSandbox, but I will explain more about how it works here in the article.
JavaScript
import { useEffect, useRef } from "react";
const useRunOnce = ({ fn, sessionKey }) => {
const triggered = useRef(false);
useEffect(() => {
const hasBeenTriggered = sessionKey
? sessionStorage.getItem(sessionKey)
: triggered.current;
if (!hasBeenTriggered) {
fn();
triggered.current = true;
if (sessionKey) {
sessionStorage.setItem(sessionKey, "true");
}
}
}, [fn, sessionKey]);
return null;
};
export default useRunOnce;
TypeScript
import React, { useEffect, useRef } from "react";
export type useRunOnceProps = {
fn: () => any;
sessionKey?: string;
};
const useRunOnce: React.FC<useRunOnceProps> = ({ fn, sessionKey }) => {
const triggered = useRef<boolean>(false);
useEffect(() => {
const hasBeenTriggered = sessionKey
? sessionStorage.getItem(sessionKey)
: triggered.current;
if (!hasBeenTriggered) {
fn();
triggered.current = true;
if (sessionKey) {
sessionStorage.setItem(sessionKey, "true");
}
}
}, [fn, sessionKey]);
return null;
};
export default useRunOnce;
Forest Gump has never heard about segmentation fault
Run Once on Mount
If you want to run a function once a component mounts, simply pass a callback function to the argument object's fn attribute. The callback will only fire one time. Unless the component is being unmounted and mounted again, in that case, it will fire again.
useRunOnce({
fn: () => {
console.log("Runs once on mount");
}
});
Run Once per Session
If you would like to run a function only one time per session, you can pass a sessionKey to the hook. The hook will then use session storage to ensure that the callback function only runs once per session.
In other words, when passing a sessionKey, the passed in function will only run one single time when a user visits your website. The callback function won't be triggered again, not even when the user reloads the website using the browser's reload button.
For the callback function to run one more time, the user will need to close the browser tab or the browser and then revisit the website in another tab or browser session. This is all according to the session storage documentation
useRunOnce({
fn: () => {
// This will not rerun when reloading the page.
console.log("Runs once per session");
},
// Session storage key ensures that the callback only runs once per session.
sessionKey: "changeMeAndFnWillRerun"
});
Note. A common problem with session and local storage is that you cannot force users to close their browsers. Although, in some cases it may be necessary to clear the storage. Easiest way to do that is to use another storage key. So, if you are using this hook with a sessionKey and want all clients to rerun the hook, even if they aren't closing their browser, just make another deployment of your application with another sessionKey.
Use Cases
When would one want to use this hook? Here are some example use cases:
- Send analytics data when a component mounts.
- Send analytics data when a user visits your site (once per session).
- Fetch data when a component mounts.
- Fetch data when a user visits your site (once per session).
- Count how many times a user visits your site.
- Run code that should run once on client side and not at all on server-side.
- Other kind of logic you want to run when a component mounts.
Regarding fetching data, there's one thing that is important to mention there. In React 18 Strict Mode, useEffects will run twice when mounting a component in development mode. For that reason, you should most often avoid sending network requests in useEffects.
It may be fine to fetch data in useEffects, but when doing that, you must ensure to handle the case when data is received twice. In other words, your callback function must be idempotent. React have documented how to handle such cases in their docs, make sure to read it, you will need to learn it eventually.
Furthermore. React supports server-side rendering (SSR), and there exist multiple frameworks that is built on React which supports SSR and even static site generation (SSG), one of those is Next.js.
When rendering React on server-side, the global window and document objects aren't available. Trying to access one of those objects on the server would throw an error. This hook can therefore be very useful when dealing with frameworks that run server-side, since this hook only will trigger the callback function on client side.
Easiest way to fix a bug is to remove code
When Not To Use
Occationally, when I think I need this hook, I think twice about it and realize that I really don't. Here follows some cases when I wouldn't use the hook.
- Write a greeting message in web console when a user first visits your page.
- Initialize a third-party library by calling one of their init-functions.
- Send analytics data when a user visits your site (and resend it when user reloads the page).
- Fetch data when a user visits your site (and refetching when user reloads the page).
The reason you may not need the hook is because it's unnecessary to use a hook/useEffect if you don't need to read or set an internal state in a component. Writing a greeting message to the web console has nothing to do with React components or its life cycle, you can do that in pure JavaScript and there is no reason to do that within a React component.
The same applies to initializing third-party libraries. That may include registering plugins to a date library, configuring languages in a i18n library or whatsoever. Such logic is rarely dependent on data in a React component and should therefore be initialized outside your components. Simply place the code in a file right above a React component and it will run once and only once, that's how ES6 modules are designed. See examples of when not to use an useEffect in Reacts documentation.
Also, I know I mentioned sending analytics data or fetching data when a user visits your site among the use cases as well. It really depends on what you want to measure or fetch. Do you want to refetch your data when the user reloads a page with the web browser's reload button?
In that case, you may be able to fetch the data outside your React components as described above, if you don't need to read or set a component's internal state.
On the other hand, if you don't want to refetch the data when a page is being reloaded, you should use the useRunOnce hook and provide a sessionKey to it.
Examples
The code below illustrates a use case when useRunOnce hook is used to fetch data when a component mounts. It stores the data to an internal state in the component and renders it when completed.
import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'
const MyComponent = () => {
const [dataToShow, setDataToShow] = useState(null)
useRunOnce({
fn: () => {
const data = fetchData()
setDataToShow(data)
}
});
return <>{dataToShow}</>
}
export default MyComponent
In the example below, when storing data to local storage, you probably don't need to use the hook. The reason is that the data isn't used in an internal state in the component. The code within the callback function is pure JavaScript and can be lifted out of the React component.
import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'
const MyComponent = () => {
useRunOnce({
fn: () => {
const data = fetchData()
localStorage.setItem('fetched-data', data)
}
});
return <>MyComponent</>
}
export default MyComponent
This is how the above code would look if we removed the hook and lifted out the code that fetches data and stores it in local storage.
import React from 'react'
import fetchData from 'services/fetchData'
const data = fetchData()
localStorage.setItem('fetched-data', data)
const MyComponent = () => {
return <>MyComponent</>
}
export default MyComponent
If we don't need to refetch the data when the website is reloaded, we could use the hook to ensure that it only stores data to local storage once per browser session, it would then look as this.
import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'
const MyComponent = () => {
useRunOnce({
fn: () => {
const data = fetchData()
localStorage.setItem('fetched-data', data)
},
sessionKey: "anyStringHere"
});
return <>MyComponent</>
}
export default MyComponent
Summary
useRunOnce is a hook you can use for two use cases.
- When you want to run some code every time a component mounts or remounts.
- When you want to run some code once per browser session.
Since the hooks wraps a useEffect, running code when a function mount can infer side effects in React 18 Strict Mode. Read React's documentation to see how to handle that.
The hook uses session storage to run code once per browser session. The hook will therefore run its code as soon as a new session is initiated, see session storage documentation for details or read through this article.
This content originally appeared on DEV Community and was authored by Dennis Persson

Dennis Persson | Sciencx (2022-07-17T14:23:40+00:00) React Hook: useRunOnce. Retrieved from https://www.scien.cx/2022/07/17/react-hook-userunonce/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.