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

@usefy/use-init

v0.2.4

Published

A React hook for one-time initialization with async support, retry, timeout, and conditional execution

Readme


Overview

@usefy/use-init is a React hook for executing initialization logic exactly once when a component mounts. It supports synchronous and asynchronous callbacks, automatic retry on failure, timeout handling, conditional execution, and cleanup functions. Perfect for initializing services, loading configuration, setting up subscriptions, and any one-time setup tasks.

Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.

Why use-init?

  • Zero Dependencies — Pure React implementation with no external dependencies
  • TypeScript First — Full type safety with exported interfaces
  • One-Time Execution — Guarantees initialization runs only once per mount
  • Async Support — Handles both synchronous and asynchronous initialization callbacks
  • Cleanup Functions — Optional cleanup function support for resource management
  • Retry Logic — Automatic retry with configurable attempts and delays
  • Timeout Handling — Built-in timeout support with custom error handling
  • Conditional Execution — Run initialization only when conditions are met
  • State Tracking — Track initialization status, loading state, and errors
  • Manual Reinitialize — Trigger re-initialization programmatically
  • SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
  • Well Tested — Comprehensive test coverage with Vitest

Installation

# npm
npm install @usefy/use-init

# yarn
yarn add @usefy/use-init

# pnpm
pnpm add @usefy/use-init

Peer Dependencies

This package requires React 18 or 19:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}

Quick Start

import { useInit } from "@usefy/use-init";

function MyComponent() {
  const { isInitialized, isInitializing, error } = useInit(async () => {
    await loadConfiguration();
    console.log("Component initialized!");
  });

  if (isInitializing) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!isInitialized) return null;

  return <div>Ready!</div>;
}

API Reference

useInit(callback, options?)

A hook that executes initialization logic exactly once when the component mounts (or when conditions are met).

Parameters

| Parameter | Type | Default | Description | | ---------- | ---------------- | ------- | ---------------------------------------- | | callback | InitCallback | — | The initialization function to run | | options | UseInitOptions | {} | Configuration options for initialization |

Callback Type

The callback can be:

  • Synchronous: () => void
  • Asynchronous: () => Promise<void>
  • With cleanup: () => void | CleanupFn or () => Promise<void | CleanupFn>

Where CleanupFn is () => void - a function that will be called when the component unmounts or before re-initialization.

Options

| Option | Type | Default | Description | | ------------ | --------- | ------- | --------------------------------------------------- | | when | boolean | true | Only run initialization when this condition is true | | retry | number | 0 | Number of retry attempts on failure | | retryDelay | number | 1000 | Delay between retry attempts in milliseconds | | timeout | number | — | Timeout for initialization in milliseconds |

Returns UseInitResult

| Property | Type | Description | | ---------------- | --------------- | -------------------------------------------------------------- | | isInitialized | boolean | Whether initialization has completed successfully | | isInitializing | boolean | Whether initialization is currently in progress | | error | Error \| null | Error that occurred during initialization, if any | | reinitialize | () => void | Manually trigger re-initialization (respects when condition) |


Examples

Basic Synchronous Initialization

import { useInit } from "@usefy/use-init";

function BasicComponent() {
  useInit(() => {
    console.log("Component initialized!");
    initializeAnalytics();
  });

  return <div>My Component</div>;
}

Async Initialization with Status Tracking

import { useInit } from "@usefy/use-init";

function DataLoader() {
  const [data, setData] = useState(null);
  const { isInitialized, isInitializing, error } = useInit(async () => {
    const response = await fetch("/api/data");
    const result = await response.json();
    setData(result);
  });

  if (isInitializing) return <div>Loading data...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!isInitialized) return null;

  return <div>{JSON.stringify(data)}</div>;
}

With Cleanup Function

import { useInit } from "@usefy/use-init";

function SubscriptionComponent() {
  useInit(() => {
    const subscription = eventBus.subscribe("event", handleEvent);

    // Return cleanup function
    return () => {
      subscription.unsubscribe();
    };
  });

  return <div>Subscribed to events</div>;
}

Conditional Initialization

import { useInit } from "@usefy/use-init";

function ConditionalComponent({ isEnabled }: { isEnabled: boolean }) {
  const { isInitialized } = useInit(
    () => {
      initializeFeature();
    },
    { when: isEnabled }
  );

  if (!isEnabled) return <div>Feature disabled</div>;
  if (!isInitialized) return <div>Initializing...</div>;

  return <div>Feature ready!</div>;
}

With Retry Logic

import { useInit } from "@usefy/use-init";

function ResilientComponent() {
  const { isInitialized, error, reinitialize } = useInit(
    async () => {
      await connectToServer();
    },
    {
      retry: 3,
      retryDelay: 1000, // Wait 1 second between retries
    }
  );

  if (error) {
    return (
      <div>
        <p>Failed to connect: {error.message}</p>
        <button onClick={reinitialize}>Retry</button>
      </div>
    );
  }

  if (!isInitialized) return <div>Connecting...</div>;

  return <div>Connected!</div>;
}

With Timeout

import { useInit } from "@usefy/use-init";

function TimeoutComponent() {
  const { isInitialized, error } = useInit(
    async () => {
      await slowOperation();
    },
    {
      timeout: 5000, // Fail after 5 seconds
    }
  );

  if (error) {
    return <div>Timeout: {error.message}</div>;
  }

  if (!isInitialized) return <div>Processing...</div>;

  return <div>Completed!</div>;
}

Combined Options: Retry + Timeout + Conditional

import { useInit } from "@usefy/use-init";

function AdvancedComponent({ shouldInit }: { shouldInit: boolean }) {
  const { isInitialized, isInitializing, error, reinitialize } = useInit(
    async () => {
      await initializeService();
    },
    {
      when: shouldInit,
      retry: 2,
      retryDelay: 2000,
      timeout: 10000,
    }
  );

  if (!shouldInit) return <div>Waiting for condition...</div>;
  if (isInitializing) return <div>Initializing (attempt in progress)...</div>;
  if (error) {
    return (
      <div>
        <p>Error: {error.message}</p>
        <button onClick={reinitialize}>Try Again</button>
      </div>
    );
  }
  if (!isInitialized) return <div>Not initialized</div>;

  return <div>Service initialized successfully!</div>;
}

Manual Re-initialization

import { useInit } from "@usefy/use-init";

function RefreshableComponent() {
  const [refreshKey, setRefreshKey] = useState(0);
  const { isInitialized, reinitialize } = useInit(async () => {
    await loadData();
  });

  const handleRefresh = () => {
    setRefreshKey((k) => k + 1);
    reinitialize();
  };

  return (
    <div>
      <button onClick={handleRefresh}>Refresh Data</button>
      {isInitialized && <div>Data loaded (key: {refreshKey})</div>}
    </div>
  );
}

Async Cleanup Function

import { useInit } from "@usefy/use-init";

function AsyncCleanupComponent() {
  useInit(async () => {
    const connection = await establishConnection();

    // Return async cleanup function
    return async () => {
      await connection.close();
      console.log("Connection closed");
    };
  });

  return <div>Connected</div>;
}

Initializing Multiple Services

import { useInit } from "@usefy/use-init";

function MultiServiceComponent() {
  const analytics = useInit(() => {
    initializeAnalytics();
    return () => analyticsService.shutdown();
  });

  const logging = useInit(async () => {
    await initializeLogging();
    return () => loggingService.disconnect();
  });

  const config = useInit(async () => {
    const config = await loadConfig();
    return config;
  });

  const allReady =
    analytics.isInitialized && logging.isInitialized && config.isInitialized;

  if (!allReady) return <div>Initializing services...</div>;

  return <div>All services ready!</div>;
}

TypeScript

This hook is written in TypeScript with full type safety.

import {
  useInit,
  type UseInitOptions,
  type UseInitResult,
} from "@usefy/use-init";

// Basic usage with type inference
const { isInitialized } = useInit(() => {
  console.log("Init");
});

// With options
const options: UseInitOptions = {
  when: true,
  retry: 3,
  retryDelay: 1000,
  timeout: 5000,
};

const result: UseInitResult = useInit(async () => {
  await initialize();
}, options);

// Cleanup function types
useInit(() => {
  const resource = createResource();
  return () => {
    // TypeScript knows this is a cleanup function
    resource.cleanup();
  };
});

Behavior Details

One-Time Execution

The hook guarantees that initialization runs only once per component mount. Even if the callback reference changes, initialization will not run again unless:

  • The component unmounts and remounts
  • reinitialize() is called manually
  • The when condition changes from false to true (after initial mount)

Conditional Execution (when)

When when is false:

  • Initialization does not run
  • If when changes from false to true, initialization will run
  • If initialization was already successful, it will not run again even if when becomes true again

Retry Logic

When retry is set to n, the hook will attempt initialization up to n + 1 times (initial attempt + n retries). Between attempts, it waits for retryDelay milliseconds.

Timeout

When timeout is set:

  • For async callbacks, a race condition is created between the callback and timeout
  • If timeout expires first, an InitTimeoutError is thrown
  • For sync callbacks, timeout is cleared immediately after execution

Cleanup Functions

If the callback returns a cleanup function:

  • It is called when the component unmounts
  • It is called before re-initialization (when reinitialize() is called)
  • It can be synchronous or asynchronous
  • Only one cleanup function is stored at a time

Error Handling

  • Errors during initialization are caught and stored in the error property
  • If retry is enabled, errors trigger retry attempts
  • After all retries fail, the final error is stored
  • Errors do not prevent component rendering

Testing

This package maintains comprehensive test coverage to ensure reliability and stability.

Test Coverage

📊 View Detailed Coverage Report (GitHub Pages)

Test Categories

  • Run callback once on mount
  • Not run callback again on re-render
  • Support synchronous callbacks
  • Support asynchronous callbacks
  • Track initialization state correctly
  • Call cleanup function on unmount
  • Call cleanup function before re-initialization
  • Support synchronous cleanup functions
  • Support asynchronous cleanup functions
  • Handle cleanup function errors gracefully
  • Not call cleanup if callback doesn't return one
  • Not run when when is false
  • Run when when changes from false to true
  • Not run again if already initialized
  • Respect when condition in reinitialize()
  • Retry on failure with correct number of attempts
  • Wait correct delay between retries
  • Stop retrying after successful attempt
  • Store final error after all retries fail
  • Not retry if component unmounts during retry
  • Timeout async callbacks that exceed timeout
  • Not timeout sync callbacks
  • Clear timeout after successful execution
  • Throw InitTimeoutError on timeout
  • Handle timeout with retry logic
  • Reinitialize when reinitialize() is called
  • Respect when condition in reinitialize()
  • Clean up previous initialization before re-running
  • Update state correctly after re-initialization
  • Track isInitializing state correctly
  • Track isInitialized state correctly
  • Track error state correctly
  • Update state only when component is mounted
  • Handle rapid state changes correctly
  • Handle component unmount during initialization
  • Handle component unmount during retry
  • Prevent concurrent initializations
  • Handle callback reference changes
  • Handle undefined/null errors gracefully

License

MIT © mirunamu

This package is part of the usefy monorepo.