useOnMount
A semantic, SSR-safe hook to execute code exactly once during component mount. Eliminates the ambiguity of empty dependency arrays and prevents linter noise.
Introduction
The useOnMount hook is a refined alternative to the standard useEffect(callback, []) pattern. While an empty dependency array technically achieves a mount-only effect, it often leads to confusion during code reviews and triggers persistent ESLint warnings. useOnMount provides a clear, declarative API that signals your intent to both the team and the compiler.
Basic Usage
import { useOnMount } from '@/hooks/useOnMount';
function Initializer() {
useOnMount(() => {
console.log("Component mounted: Initializing persistent resources.");
});
return <div>Component is active.</div>;
}API Reference
Parameters
Prop
Type
Returns
void
Hook
'use client';
import { useEffect } from 'react';
const EMPTY = [] as unknown[];
/**
* A React.useEffect equivalent that runs once, when the component is mounted.
*/
export function useOnMount(effect: React.EffectCallback) {
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(effect, EMPTY);
/* eslint-enable react-hooks/exhaustive-deps */
}Advanced Examples
Initializing Third-Party Libraries
Many non-React libraries (like Mapbox, Canvas engines, or Charting libs) require a manual initialization step that should never be re-triggered by component updates.
import { useRef } from 'react';
import { useOnMount } from '@/hooks/useOnMount';
import { MyChartLib } from 'third-party-library';
export function ChartComponent() {
const chartRef = useRef<HTMLDivElement>(null);
useOnMount(() => {
const chart = MyChartLib.init(chartRef.current);
return () => chart.destroy(); // Cleanup works exactly like useEffect
});
return <div ref={chartRef} />;
}Event Bus Subscriptions
When working with global event systems or BroadcastChannel, we want to subscribe exactly once during the lifecycle.
import { useOnMount } from '@/hooks/useOnMount';
export function NotificationListener() {
useOnMount(() => {
const handleMessage = (e: MessageEvent) => console.log(e.data);
const channel = new BroadcastChannel('app_updates');
channel.onmessage = handleMessage;
return () => channel.close();
});
return null;
}Why use this?
Developer Intent & Readability
Standard useEffect(fn, []) is often a source of friction. Juniors might wonder if the empty array was a mistake, and seniors have to verify if dependencies were omitted intentionally. useOnMount replaces "how we do it" with "what we are doing."
Silencing the Linter Safely
The react-hooks/exhaustive-deps rule is vital, but for specific initialization logic, it often flags variables that technically shouldn't trigger a re-run.
Architecture Note
By isolating the eslint-disable directive inside this hook, we keep our business components clean. We avoid littering our codebase with linter-disable comments, ensuring that the rest of your app remains strictly checked.
Memory Efficiency
The hook uses a static EMPTY constant defined outside the function scope. This prevents the allocation of a new array literal [] on every single render of every component using this hook, providing a micro-optimization for high-frequency rendering scenarios.
Error Handling
Strict Mode Behavior
In React 18+ Development Mode, your mount effects will run twice. This is intentional behavior by the React team to help you surface missing cleanup functions. useOnMount does not bypass this check; ensure your fn returns a proper cleanup if it performs side effects.
Common Pitfalls
- Don't use this if you need to react to prop changes. If your logic depends on a value that changes over time, use the standard
useEffect. - Don't use this for data fetching if you are using React 19; consider the
useAPI or a library like TanStack Query for better integration with Suspense.
Last updated on