useIsomorphicEffect uses useLayoutEffect in the browser and useEffect on the server. This is a drop-in replacement when you need layout effects without SSR warnings.
Usage
import { useIsomorphicEffect } from '@kivora/react';
function MyComponent() {
useIsomorphicEffect(() => {
// Runs as useLayoutEffect in browser
// Runs as useEffect on server (no warning)
}, []);
return <div>Content</div>;
}
Parameters
effect
() => void | (() => void)
required
The effect function to run. Can optionally return a cleanup function.
Optional dependency array, same as useEffect or useLayoutEffect.
Returns
void
Examples
Measuring DOM Elements
function MeasuredBox() {
const [height, setHeight] = useState(0);
const ref = useRef<HTMLDivElement>(null);
useIsomorphicEffect(() => {
if (ref.current) {
// Measure synchronously before paint (in browser)
setHeight(ref.current.getBoundingClientRect().height);
}
}, []);
return (
<div>
<div ref={ref}>Content to measure</div>
<p>Height: {height}px</p>
</div>
);
}
Synchronizing with External Libraries
function ChartComponent({ data }: { data: number[] }) {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart>();
useIsomorphicEffect(() => {
if (!chartRef.current) return;
// Initialize chart synchronously before paint
chartInstance.current = new Chart(chartRef.current, {
type: 'line',
data: { datasets: [{ data }] },
});
return () => {
chartInstance.current?.destroy();
};
}, []);
useIsomorphicEffect(() => {
// Update chart data synchronously
if (chartInstance.current) {
chartInstance.current.data.datasets[0].data = data;
chartInstance.current.update();
}
}, [data]);
return <canvas ref={chartRef} />;
}
Managing Focus
function AutoFocusInput({ shouldFocus }: { shouldFocus: boolean }) {
const inputRef = useRef<HTMLInputElement>(null);
useIsomorphicEffect(() => {
if (shouldFocus && inputRef.current) {
// Focus synchronously before paint to avoid flicker
inputRef.current.focus();
}
}, [shouldFocus]);
return <input ref={inputRef} />;
}
function ScrollPosition() {
const [scrollY, setScrollY] = useState(0);
useIsomorphicEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY);
};
// Read initial scroll position synchronously
handleScroll();
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div>Scroll position: {scrollY}px</div>;
}
Preventing Layout Shift
function DynamicHeight({ content }: { content: string }) {
const [minHeight, setMinHeight] = useState<number>();
const ref = useRef<HTMLDivElement>(null);
useIsomorphicEffect(() => {
if (ref.current) {
// Set min-height synchronously to prevent layout shift
const height = ref.current.getBoundingClientRect().height;
setMinHeight(height);
}
}, [content]);
return (
<div ref={ref} style={{ minHeight }}>
{content}
</div>
);
}
Animation Setup
function AnimatedComponent({ isVisible }: { isVisible: boolean }) {
const ref = useRef<HTMLDivElement>(null);
useIsomorphicEffect(() => {
if (!ref.current) return;
// Set up animation synchronously before paint
const animation = ref.current.animate(
[
{ opacity: 0, transform: 'translateY(-10px)' },
{ opacity: 1, transform: 'translateY(0)' },
],
{ duration: 300, fill: 'forwards' }
);
if (!isVisible) {
animation.reverse();
}
return () => animation.cancel();
}, [isVisible]);
return <div ref={ref}>Animated content</div>;
}
Notes
- Resolves to
useLayoutEffect in browser environments (when window is defined)
- Resolves to
useEffect on the server to avoid SSR warnings
- Use this instead of
useLayoutEffect when building SSR-compatible components
- The effect timing matches
useLayoutEffect in the browser (synchronous after DOM mutations, before paint)
- Ideal for measurements, DOM manipulations, and synchronous updates that must happen before paint