npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@utilityjs/use-isomorphic-layout-effect

v2.0.0

Published

A React hook that schedules a `React.useLayoutEffect` with a fallback to a `React.useEffect` for environments where layout effects should not be used (such as server-side rendering).

Downloads

217

Readme

UtilityJS | useIsomorphicLayoutEffect

A React hook that schedules a React.useLayoutEffect with a fallback to a React.useEffect for environments where layout effects should not be used (such as server-side rendering).

Features

  • SSR Safe: Prevents hydration mismatches and SSR warnings
  • Client Optimization: Uses useLayoutEffect for synchronous DOM updates on client
  • Server Compatibility: Falls back to useEffect on server environments
  • Drop-in Replacement: Same API as useLayoutEffect
  • TypeScript Support: Full type safety with proper TypeScript definitions
  • Zero Runtime Cost: Simple conditional export with no overhead

Installation

npm install @utilityjs/use-isomorphic-layout-effect

or

pnpm add @utilityjs/use-isomorphic-layout-effect

Usage

Basic Usage

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef, useState } from "react";

function MeasuredComponent() {
  const ref = useRef<HTMLDivElement>(null);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useIsomorphicLayoutEffect(() => {
    if (ref.current) {
      // This runs synchronously after DOM mutations on client
      // but as regular effect on server to avoid SSR issues
      const { offsetWidth, offsetHeight } = ref.current;
      setDimensions({ width: offsetWidth, height: offsetHeight });
    }
  }, []);

  return (
    <div
      ref={ref}
      style={{ padding: "20px", border: "1px solid #ccc" }}
    >
      <p>Width: {dimensions.width}px</p>
      <p>Height: {dimensions.height}px</p>
    </div>
  );
}

DOM Manipulation

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef } from "react";

function FocusedInput({ autoFocus }: { autoFocus?: boolean }) {
  const inputRef = useRef<HTMLInputElement>(null);

  useIsomorphicLayoutEffect(() => {
    if (autoFocus && inputRef.current) {
      // Focus input synchronously on client, safely on server
      inputRef.current.focus();
    }
  }, [autoFocus]);

  return (
    <input
      ref={inputRef}
      placeholder="Type here..."
    />
  );
}

Scroll Position Management

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRouter } from "next/router";

function ScrollToTop() {
  const router = useRouter();

  useIsomorphicLayoutEffect(() => {
    // Scroll to top on route change, but only on client
    if (typeof window !== "undefined") {
      window.scrollTo(0, 0);
    }
  }, [router.pathname]);

  return null;
}

Dynamic Styling

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef, useState } from "react";

function ResponsiveText() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [fontSize, setFontSize] = useState(16);

  useIsomorphicLayoutEffect(() => {
    const updateFontSize = () => {
      if (containerRef.current) {
        const containerWidth = containerRef.current.offsetWidth;
        // Adjust font size based on container width
        const newFontSize = Math.max(12, Math.min(24, containerWidth / 20));
        setFontSize(newFontSize);
      }
    };

    updateFontSize();
    window.addEventListener("resize", updateFontSize);

    return () => window.removeEventListener("resize", updateFontSize);
  }, []);

  return (
    <div
      ref={containerRef}
      style={{ width: "100%", border: "1px solid #ccc" }}
    >
      <p style={{ fontSize: `${fontSize}px`, margin: 0 }}>
        This text scales with container width
      </p>
    </div>
  );
}

Animation Setup

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef, useState } from "react";

function AnimatedBox() {
  const boxRef = useRef<HTMLDivElement>(null);
  const [isVisible, setIsVisible] = useState(false);

  useIsomorphicLayoutEffect(() => {
    if (boxRef.current) {
      // Set initial state synchronously to prevent flash
      boxRef.current.style.transform = "translateY(20px)";
      boxRef.current.style.opacity = "0";

      // Trigger animation
      const timer = setTimeout(() => {
        if (boxRef.current) {
          boxRef.current.style.transition = "all 0.3s ease";
          boxRef.current.style.transform = "translateY(0)";
          boxRef.current.style.opacity = "1";
          setIsVisible(true);
        }
      }, 100);

      return () => clearTimeout(timer);
    }
  }, []);

  return (
    <div
      ref={boxRef}
      style={{
        width: "200px",
        height: "100px",
        backgroundColor: "#007bff",
        color: "white",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      {isVisible ? "Animated!" : "Loading..."}
    </div>
  );
}

Canvas Setup

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef } from "react";

function Canvas() {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useIsomorphicLayoutEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    // Set canvas size to match display size
    const rect = canvas.getBoundingClientRect();
    canvas.width = rect.width * window.devicePixelRatio;
    canvas.height = rect.height * window.devicePixelRatio;
    ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

    // Draw something
    ctx.fillStyle = "#007bff";
    ctx.fillRect(10, 10, 100, 50);
    ctx.fillStyle = "white";
    ctx.font = "16px Arial";
    ctx.fillText("Canvas", 20, 35);
  }, []);

  return (
    <canvas
      ref={canvasRef}
      style={{ width: "200px", height: "100px", border: "1px solid #ccc" }}
    />
  );
}

Third-party Library Integration

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef } from "react";

function ChartComponent({ data }: { data: number[] }) {
  const chartRef = useRef<HTMLDivElement>(null);

  useIsomorphicLayoutEffect(() => {
    if (!chartRef.current || typeof window === "undefined") return;

    // Dynamically import chart library only on client
    import("chart.js").then(({ Chart, registerables }) => {
      Chart.register(...registerables);

      const canvas = document.createElement("canvas");
      chartRef.current!.appendChild(canvas);

      const chart = new Chart(canvas, {
        type: "line",
        data: {
          labels: data.map((_, i) => `Point ${i + 1}`),
          datasets: [
            {
              label: "Data",
              data: data,
              borderColor: "#007bff",
              backgroundColor: "rgba(0, 123, 255, 0.1)",
            },
          ],
        },
      });

      return () => chart.destroy();
    });
  }, [data]);

  return (
    <div
      ref={chartRef}
      style={{ width: "400px", height: "200px" }}
    />
  );
}

Custom Hook Integration

import { useIsomorphicLayoutEffect } from "@utilityjs/use-isomorphic-layout-effect";
import { useRef, useState } from "react";

function useElementSize<T extends HTMLElement>() {
  const ref = useRef<T>(null);
  const [size, setSize] = useState({ width: 0, height: 0 });

  useIsomorphicLayoutEffect(() => {
    const element = ref.current;
    if (!element) return;

    const updateSize = () => {
      setSize({
        width: element.offsetWidth,
        height: element.offsetHeight,
      });
    };

    // Initial measurement
    updateSize();

    // Set up ResizeObserver if available
    if (typeof ResizeObserver !== "undefined") {
      const resizeObserver = new ResizeObserver(updateSize);
      resizeObserver.observe(element);
      return () => resizeObserver.disconnect();
    }

    // Fallback to window resize
    window.addEventListener("resize", updateSize);
    return () => window.removeEventListener("resize", updateSize);
  }, []);

  return { ref, size };
}

function SizeAwareComponent() {
  const { ref, size } = useElementSize<HTMLDivElement>();

  return (
    <div
      ref={ref}
      style={{
        width: "50%",
        minHeight: "100px",
        backgroundColor: "#f0f0f0",
        padding: "20px",
        resize: "both",
        overflow: "auto",
      }}
    >
      <p>Width: {size.width}px</p>
      <p>Height: {size.height}px</p>
      <p>Resize me!</p>
    </div>
  );
}

API

useIsomorphicLayoutEffect(effect: EffectCallback, deps?: DependencyList): void

A hook that uses useLayoutEffect on the client and useEffect on the server.

Parameters

  • effect (EffectCallback): The effect function to run
  • deps (DependencyList, optional): Dependency array for the effect

Returns

  • void: No return value

Example

useIsomorphicLayoutEffect(() => {
  // Effect code here
  return () => {
    // Cleanup code here
  };
}, [dependency]);

When to Use

Use useIsomorphicLayoutEffect when:

  • DOM Measurements: Reading layout properties like offsetWidth, scrollTop
  • Synchronous DOM Updates: Preventing visual flicker during updates
  • Animation Setup: Setting initial styles before animations
  • Focus Management: Focusing elements immediately after render
  • Third-party Libraries: Integrating libraries that need DOM access
  • SSR Applications: Any layout effect in server-rendered apps

Use regular useEffect when

  • Data Fetching: Async operations that don't affect layout
  • Event Listeners: Setting up non-layout related listeners
  • Timers: setTimeout/setInterval that don't affect DOM
  • Side Effects: Operations that don't need to be synchronous

SSR Considerations

  • Hydration Safety: Prevents React hydration warnings
  • Performance: Avoids unnecessary work on the server
  • Compatibility: Works with Next.js, Gatsby, and other SSR frameworks
  • Progressive Enhancement: Gracefully handles server-to-client transition

Contributing

Read the contributing guide to learn about our development process, how to propose bug fixes and improvements, and how to build and test your changes.

License

This project is licensed under the terms of the MIT license.