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

@okyrychenko-dev/react-effect-when

v1.2.0

Published

Declarative conditional effects for React with less Strict Mode noise in development

Readme

@okyrychenko-dev/react-effect-when

npm version npm downloads License: MIT

Declarative conditional effects for React, with less boilerplate and less development noise

react-effect-when helps you run effects only when dependencies reach the state you actually care about. Its main value is expressing conditional effects declaratively, while also removing repeated useRef guards, if-based boilerplate, and some development noise around gated effects in React Strict Mode.

What Problem It Solves

In real apps, many effects are not meant to run "on every mount-like moment". They should run only when something meaningful becomes true:

  • a user and socket are both ready
  • a modal is actually open
  • analytics should fire once
  • a subscription should start only after auth is available

Teams often respond by:

  • disabling Strict Mode
  • adding ad hoc useRef(false) guards
  • pushing conditional logic deep inside useEffect

react-effect-when gives you a cleaner option: express effect timing declaratively instead of repeating local guard logic in every component.

Main Goals

  • Replace repetitive useRef guards and early-return boilerplate with a declarative API
  • Run effects only when dependencies are actually ready, truthy, or match a custom predicate
  • Keep effect intent readable at the call site instead of hiding conditions inside the effect body
  • Preserve predictable cleanup behavior and a familiar React mental model
  • Provide strong TypeScript support for readiness and predicate-based narrowing
  • Reduce some Strict Mode-related development noise in gated-effect scenarios without turning Strict Mode off

Why Use It

  • Run an effect only when predicate(deps) becomes true
  • Avoid repeating if (!user || !socket) return across components
  • Reduce extra development noise around initialization, analytics, sockets, and one-time side effects
  • Re-run only on meaningful matches with once: false
  • Use useEffectWhenReady and useEffectWhenTruthy for common typed cases
  • Keep public imports simple through the root package API

Installation

npm install @okyrychenko-dev/react-effect-when
# or
yarn add @okyrychenko-dev/react-effect-when
# or
pnpm add @okyrychenko-dev/react-effect-when

This package requires the following peer dependencies:

Quick Start

import { useEffectWhenReady } from "@okyrychenko-dev/react-effect-when";

function Dashboard() {
  const [user, setUser] = useState<User | null>(null);
  const [socket, setSocket] = useState<Socket | null>(null);

  useEffectWhenReady(
    ([readyUser, readySocket]) => {
      readySocket.emit("identify", readyUser.id);
      return () => readySocket.emit("leave", readyUser.id);
    },
    [user, socket],
    { once: false }
  );
}

Strict Mode In Development

This library can reduce some development noise when a side effect should run only after a meaningful condition is satisfied.

Common examples:

  • analytics and tracking calls
  • one-time fire-and-forget effects
  • notifications and toasts
  • WebSocket or channel initialization after auth is ready
  • effects that should wait for fully ready data

The goal is not to fight React or replace useEffect. The goal is to make effect timing explicit and convenient in the cases where plain useEffect becomes noisy or repetitive.

This is not a global fix for Strict Mode re-mount behavior. With once: true, the effect runs once per mount lifecycle after the predicate first matches. That is useful for fire-and-forget effects, but it is usually the wrong setting for long-lived resources that return cleanup functions.

Core Concepts

  • useEffectWhen is the base hook. It receives the current dependency tuple and runs only when your predicate returns true.
  • once: true is the default. It means the effect runs once per mount lifecycle after the predicate first matches.
  • once: false means the effect re-runs every time dependencies change and the predicate matches again. Use this for subscriptions, sockets, event listeners, timers, and other resource effects that return cleanup.
  • useEffectWhenReady is the fastest path when all dependencies must be non-null and non-undefined.
  • useEffectWhenTruthy is the fastest path when all dependencies must be truthy.
  • predicates.ready, predicates.truthy, and predicates.always are reusable building blocks for the base hook.

once Semantics

once controls whether a matched effect may run again in the same mount lifecycle.

Use once: true for effects that should happen once after a condition becomes true and do not need to keep a resource alive:

  • analytics and tracking calls
  • notifications and toasts
  • imperative one-time callbacks
  • idempotent initialization without cleanup

Use once: false for effects that create a resource and return cleanup:

  • WebSocket or channel connections
  • event listeners and subscriptions
  • intervals, timeouts, and animation loops
  • effects that must follow changed dependency values after the first match

When once: true, React may still call the previous cleanup during dependency changes, unmounts, or Strict Mode development checks. The hook will not run the setup again in that same mount lifecycle after it has already matched once. For resource effects, this can leave the resource cleaned up but not recreated. Prefer once: false whenever the returned cleanup tears down something that should remain active while the component is mounted.

Core Use Cases

Wait for async readiness

Use useEffectWhenReady when data, services, or refs load independently and the effect should wait until everything is available.

Remove repeated guard boilerplate

Use useEffectWhen when your current useEffect bodies mostly start with early returns and setup checks.

Re-run only on meaningful matches

Use once: false when you want the effect to run every time a threshold or condition is satisfied again, or when the effect owns a resource that must be cleaned up and recreated.

Reduce gated-effect dev noise

Use useEffectWhen when a side effect should run only after a meaningful condition is satisfied instead of putting repeated guards inside the effect body.

Comparison

| | Plain useEffect | Generic effect helper | @okyrychenko-dev/react-effect-when | | ----------------------------------------- | ------------------------------- | --------------------- | ------------------------------------ | | Conditional effect execution | Manual guards inside the effect | Usually supported | Built-in | | Wait for non-null async readiness | Manual guards | Varies | useEffectWhenReady | | Wait for truthy values | Manual guards | Varies | useEffectWhenTruthy | | Skip the initial mount | Manual useRef guard | Varies | useEffectWhenChanged | | Repeat only on meaningful matches | Manual branching | Varies | once: false | | Access current deps tuple in the callback | Manual closure usage | Varies | Built-in | | Observe skipped states | Manual logging | Rare | onSkip | | Root-level simple public API | Native React only | Varies | Yes |

Key Benefits

  • Clear intent: the condition for running the effect is visible at the call site
  • Less boilerplate: fewer manual refs, flags, and nested guards
  • Better dev ergonomics: less local effect boilerplate and less noise around gated effects
  • Familiar semantics: still built on top of normal React effect behavior
  • Typed readiness helpers: better safety when dependencies become available

When To Use It

  • Your useEffect usually starts with guards like if (!user || !socket) return
  • You would otherwise add useRef flags for fire-and-forget effects that should run once after a condition is met
  • Your effect should wait until values are ready, truthy, or match a custom predicate
  • You want cleanup behavior to stay explicit while the trigger condition stays readable
  • You want a cleaner way to gate effects during development without disabling StrictMode

When Not To Use It

  • A plain useEffect already expresses the behavior clearly
  • The effect should always run for every dependency change with no gating
  • The condition belongs in derived state or render logic rather than in an effect
  • You are trying to bypass real remount semantics or "fix" React Strict Mode globally
  • You need once: true to keep a cleanup-backed resource alive after React has cleaned it up

Why Not Just Use useEffect + if + useRef

You can. For a one-off case, that is often fine.

The problem appears when the same pattern repeats across a codebase:

  • if guards hide the real trigger condition inside the effect body
  • useRef(false) flags add boilerplate and are easy to get wrong
  • intent becomes inconsistent from component to component
  • Strict Mode double invoke pain gets handled with ad hoc local workarounds

react-effect-when gives that pattern one explicit API instead of many custom versions.

Important Boundary

This library does not disable React Strict Mode, patch React behavior, or guarantee perfectly identical production behavior in every scenario.

What it does is make effect timing explicit and ergonomic in the cases where you want to avoid repeating local guard logic and reduce unnecessary development noise around conditional effects.

API Reference

Public imports

Use the root package import for all documented APIs:

import {
  createEffectWhen,
  predicates,
  useEffectWhen,
  useEffectWhenChanged,
  useEffectWhenReady,
  useEffectWhenTruthy,
} from "@okyrychenko-dev/react-effect-when";

Hooks

Start with these first:

  • useEffectWhen
  • useEffectWhenChanged
  • useEffectWhenReady
  • useEffectWhenTruthy

Use createEffectWhen when the same predicate repeats across multiple components and deserves a named reusable hook.

useEffectWhenChanged(effect, deps)

Runs an effect only after the initial mount, when deps change.

Use this when you want update-only behavior without repeating a useRef(true) guard in each component.

This hook does not debounce or throttle updates. If your input changes rapidly, the effect still runs once per changed render.

Parameters:

  • effect: (deps: T) => void | (() => void) - Same cleanup semantics as useEffect, plus access to the current dependency tuple
  • deps: T extends DependencyList

Example:

import { useEffectWhenChanged } from "@okyrychenko-dev/react-effect-when";

function Search({ query }: { query: string }) {
  useEffectWhenChanged(
    ([nextQuery]) => {
      trackSearchChange(nextQuery);
    },
    [query]
  );
}

useEffectWhen(effect, deps, predicate, options?)

Runs an effect only when predicate(deps) returns true.

Parameters:

  • effect: (deps: T) => void | (() => void) - Same cleanup semantics as useEffect, plus access to the current dependency tuple
  • deps: T extends DependencyList - Passed to React and to the predicate
  • predicate: (deps: T) => boolean - Condition that controls when the effect runs
  • options?: UseEffectWhenOptions<T>
    • once?: boolean - Defaults to true. Run once after the first match, or set once: false to re-run on every matching dependency change
    • onSkip?: (deps: T) => void - Called when dependencies change and the predicate returns false; stops firing after the effect runs if once: true

effect follows normal useEffect cleanup semantics, but once: true prevents the setup from running again after the first successful match in the same mount lifecycle. If the effect returns cleanup for a long-lived resource, pass { once: false } so React can clean up the previous resource and recreate the next one when needed.

By design, predicate, onSkip, and once are kept fresh via refs, so they do not need to appear in the dependency array.

Example:

import { useEffectWhen } from "@okyrychenko-dev/react-effect-when";

function Game({ score }: { score: number }) {
  useEffectWhen(
    ([currentScore]) => {
      showConfetti(currentScore);
    },
    [score],
    ([value]) => value > 100,
    { once: false }
  );
}

useEffectWhenReady(effect, deps, options?)

Runs the effect when all dependency values are non-null and non-undefined.

Parameters:

  • effect: (deps: ReadyDeps<T>) => void | (() => void)
  • deps: T extends DependencyList
  • options?: UseEffectWhenOptions<T>

Example:

import { useEffectWhenReady } from "@okyrychenko-dev/react-effect-when";

function Profile({ user, token }: { user: User | null; token: string | null }) {
  useEffectWhenReady(
    ([readyUser, readyToken]) => {
      trackProfileView(readyUser.id, readyToken);
    },
    [user, token]
  );
}

useEffectWhenTruthy(effect, deps, options?)

Runs the effect when all dependency values are truthy.

Parameters:

  • effect: (deps: TruthyDeps<T>) => void | (() => void)
  • deps: T extends DependencyList
  • options?: UseEffectWhenOptions<T>

Example:

import { useEffectWhenTruthy } from "@okyrychenko-dev/react-effect-when";

function SessionBanner({ token, isOnline }: { token: string | null; isOnline: boolean }) {
  useEffectWhenTruthy(
    ([readyToken, online]) => {
      connectBannerChannel(readyToken, online);
    },
    [token, isOnline],
    { once: false }
  );
}

createEffectWhen(predicate)

Creates a reusable hook with a baked-in predicate.

Use this when multiple components share the same condition and you want a named hook instead of repeating the predicate inline.

Parameters:

  • predicate: (deps: T) => boolean

Returns:

  • (effect: (deps: T) => void | (() => void), deps: T, options?: UseEffectWhenOptions<T>) => void

If predicate is a type guard, the returned hook preserves narrowed dependency types inside effect.

Example:

import { createEffectWhen, type ReadyDeps } from "@okyrychenko-dev/react-effect-when";

const useEffectWhenAuthed = createEffectWhen<
  [User | null, string | null],
  ReadyDeps<[User | null, string | null]>
>(
  (deps): deps is ReadyDeps<[User | null, string | null]> =>
    deps[0] !== null && deps[1] !== null
);

function Dashboard({ user, token }: { user: User | null; token: string | null }) {
  useEffectWhenAuthed(
    ([readyUser, readyToken]) => {
      initializeDashboard(readyUser.id, readyToken);
    },
    [user, token]
  );
}

Example with type narrowing:

import { createEffectWhen, predicates, type ReadyDeps } from "@okyrychenko-dev/react-effect-when";

const useEffectWhenReady = createEffectWhen<
  [User | null, Socket | null],
  ReadyDeps<[User | null, Socket | null]>
>((deps): deps is ReadyDeps<[User | null, Socket | null]> => predicates.ready(deps));

function Connection({ user, socket }: { user: User | null; socket: Socket | null }) {
  useEffectWhenReady(
    ([readyUser, readySocket]) => {
      readySocket.emit("identify", readyUser.id);
    },
    [user, socket]
  );
}

Shared hook example:

// hooks/useEffectWhenAuthed.ts
import { createEffectWhen, type ReadyDeps } from "@okyrychenko-dev/react-effect-when";

export const useEffectWhenAuthed = createEffectWhen<
  [User | null, string | null],
  ReadyDeps<[User | null, string | null]>
>(
  (deps): deps is ReadyDeps<[User | null, string | null]> =>
    deps[0] !== null && deps[1] !== null
);
// Dashboard.tsx
import { useEffectWhenAuthed } from "./hooks";

function Dashboard({ user, token }: { user: User | null; token: string | null }) {
  useEffectWhenAuthed(
    ([readyUser, readyToken]) => {
      initializeDashboard(readyUser.id, readyToken);
    },
    [user, token]
  );
}
// Notifications.tsx
import { useEffectWhenAuthed } from "./hooks";

function Notifications({ user, token }: { user: User | null; token: string | null }) {
  useEffectWhenAuthed(
    ([readyUser, readyToken]) => {
      connectNotifications(readyUser.id, readyToken);
    },
    [user, token],
    { once: false }
  );
}

Built-in predicates

  • predicates.ready(deps) - true when all dependencies are non-null and non-undefined
  • predicates.truthy(deps) - true when all dependencies are truthy
  • predicates.always(deps) - always true, equivalent to a plain useEffect

Example:

import { predicates, useEffectWhen } from "@okyrychenko-dev/react-effect-when";

useEffectWhen(
  ([currentUser, currentToken]) => {
    initializeDashboard(currentUser, currentToken);
  },
  [user, token],
  predicates.ready,
  {
    onSkip: ([pendingUser, pendingToken]) => {
      console.debug("Waiting for deps:", {
        user: pendingUser,
        token: pendingToken,
      });
    },
  }
);

Real-World Examples

Prevent analytics from firing twice in development

import { useEffectWhen } from "@okyrychenko-dev/react-effect-when";

function ProductPage({ productId, isReady }: { productId: string; isReady: boolean }) {
  useEffectWhen(
    ([id]) => {
      analytics.track("product_view", { productId: id });
    },
    [productId, isReady],
    ([, ready]) => ready === true
  );
}

This is a common React Strict Mode double-invoke pain point in development when analytics should fire only after the page is actually ready.

Initialize a WebSocket only when auth is ready

import { useEffectWhenReady } from "@okyrychenko-dev/react-effect-when";

function RealtimeConnection({
  userId,
  authToken,
}: {
  userId: string | null;
  authToken: string | null;
}) {
  useEffectWhenReady(
    ([readyUserId, readyToken]) => {
      const socket = connectSocket({ userId: readyUserId, token: readyToken });

      return () => {
        socket.close();
      };
    },
    [userId, authToken],
    { once: false }
  );
}

This keeps WebSocket setup declarative and avoids scattering if (!userId || !authToken) return checks through the effect body. Because the effect opens a connection and returns cleanup, it uses once: false so the connection can be recreated after dependency changes or Strict Mode development checks.

Show a toast only when a modal actually opens

import { useEffectWhen } from "@okyrychenko-dev/react-effect-when";

function Modal({ isOpen }: { isOpen: boolean }) {
  useEffectWhen(
    ([open]) => {
      toast.info("Modal opened");
    },
    [isOpen],
    ([open]) => open === true
  );
}

This is useful when development re-mounts would otherwise create extra toast or notification noise.

More Examples

Run once when a modal opens

import { useEffectWhen } from "@okyrychenko-dev/react-effect-when";

function Modal({ isOpen }: { isOpen: boolean }) {
  useEffectWhen(
    ([open]) => {
      fetchModalData(open);
    },
    [isOpen],
    ([open]) => open === true
  );
}

Sync only when a custom predicate matches

import { useEffectWhen } from "@okyrychenko-dev/react-effect-when";

useEffectWhen(
  ([itemList]) => {
    syncToServer(itemList);
  },
  [items, isOnline, hasPermission],
  ([itemList, online, permission]) => online === true && permission === true && itemList.length > 0
);

Observe skipped states with onSkip

import { predicates, useEffectWhen } from "@okyrychenko-dev/react-effect-when";

useEffectWhen(
  ([currentUser, currentToken]) => {
    initializeDashboard(currentUser, currentToken);
  },
  [user, token],
  predicates.ready,
  {
    onSkip: ([pendingUser, pendingToken]) => {
      console.debug("Still waiting:", {
        user: pendingUser,
        token: pendingToken,
      });
    },
  }
);

Internal Demo

The repository also contains a few internal demo snippets:

  • examples/basic-ready.tsx
  • examples/custom-predicate.tsx
  • examples/truthy-repeat.tsx

Use them for local experimentation only. The README examples are the canonical public documentation for package usage.

Behavior Reference

| Scenario | Behavior | | ----------------------------------------------------- | -------------------------------------------------- | | Predicate false on mount | Effect does not run | | Predicate becomes true | Effect runs | | Deps change after effect ran (once: true) | Previous cleanup may run; setup does not re-run | | Deps toggle back to falsy, then truthy (once: true) | Effect does not re-run | | Predicate true again (once: false) | Effect re-runs and the previous cleanup runs first | | Component unmounts | Cleanup runs once | | React 18 Strict Mode remount in dev | Same behavior as a fresh mount |

Important Semantics

  • predicate and onSkip are stored in refs, so the hook always uses their latest version without adding them to the dependency array.
  • once is also kept fresh internally, so cleanup logic follows the latest option value across renders.
  • Only deps control when React re-runs the effect. Changing predicate or onSkip alone does not trigger a re-run.
  • With once: true, cleanup does not reset the "already ran" state. This preserves once-only behavior for fire-and-forget effects, but it means cleanup-backed resources should usually use once: false.
  • useEffectWhen passes the current dependency tuple into effect.
  • useEffectWhenReady and useEffectWhenTruthy are the preferred APIs when you want narrowed values for the common built-in conditions.
  • In React Strict Mode, behavior is still scoped per mount lifecycle. A real remount is treated as a fresh hook instance.
  • Public imports are exposed from the package root. Subpath imports are not required for the documented API.

Publish Checklist

Before publishing a new version, make sure this command passes:

npm run release:check

License

MIT © Oleksii Kyrychenko