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

react-use-suspendable

v0.2.0

Published

This is a hook to create a "suspendable" -- a promise cached to the current element's structure, in such a way that when the component gets destroyed and recreated, it will be preserved.

Readme

use-suspendable

This is a hook to create a "suspendable" -- a promise cached to the current element's structure, in such a way that when the component gets destroyed and recreated, it will be preserved.

This is useful when using it to suspend.

[!WARNING]
This was designed to solve the problem where async requests are made in effects, not other uses of async with React.

Migration from setting state in useEffect

Otherwise known as a cascading render, setting state in an effect is bad behavior, because effects are activated by... state updates.

Read more about this in the react docs.

Old version:

import {useEffect, useState} from 'react';

function MyComponent({param}) {
    const [data, setData] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(()=>{
        expensiveAsyncFunction(param)
            .then((result)=>{
                setData(result);
            })
            .finally(()=>{
                setIsLoading(false);
            })
    }, [param]);

    // more hook code here

    return (
        <>
            {
                isLoading ? 
                    <LoadingState /> :
                    <DataRenderer data={data}>
            }
        </>
    );
}

New version w/ use ("react": ">=19.0.0"):

import {use} from 'react';
import useSuspendable from 'react-use-suspendable';

function MyComponent({param, ...passThroughProps}) {
    const [promise] = useSuspendable(
        ()=>expensiveAsyncFunction(param),
        [param]
    );
    const data = use(promise);

    // more hook code here

    return (
        <DataRenderer
            data={data}
            {...passThroughProps}
        />
    );
}

function MyComponentContainer(props) {
    return (
        <Suspense fallback={<LoadingState />}>
            <MyComponent {...props} />
        </Suspense>
    );
}

"react": ">=16.8 <19"

You can use use-suspendable/map-promise or use-suspendable/wrap-promise or another promise synchronizer such as p-state.

import useSuspendable from 'react-use-suspendable';
import wrapPromise, {
  getValue,
  getReason,
  isDone,
  isRejected,
} from 'react-use-suspendable/wrap-promise';

function MyComponent({param, ...passThroughProps}) {
    const [promise] = useSuspendable(
        ()=>wrapPromise(expensiveAsyncFunction(param)),
        [param]
    );

    if (isRejected(madePromise)) {
        throw getReason(madePromise); // throwing the error
    }

    if (!isDone(madePromise)) {
        throw madePromise;
    }

    const data = getValue(madePromise);

    // more hook code here

    return (
        <DataRenderer
            data={data}
            {...passThroughProps}
        />
    );
}

function MyComponentContainer(props) {
    return (
        <Suspense fallback={<LoadingState />}>
            <MyComponent {...props} />
        </Suspense>
    );
}

use-suspendable/wrap-promise and use-suspendable/map-promise have the same API, but work different internally -- wrap-promise will modify the promise to store its state while map-promise will store the promise in a WeakMap at the module-level.

What's the difference?

  • In the old version, we:
    1. rendered a loading state
    2. waited for first paint
    3. started fetching data
    4. once the data was fetched, we updated state
  • In the new version, we:
    1. immediately started fetching data without waiting for a paint
    2. suspended while that data fetch had already started
    3. painted when the data came back

Other benefits

  • Delegated loading state higher up the component tree (MyComponentContainer in this case), allowing your component to handle just the rendering logic
  • If the promise errors, you're leaving it up to React to decide how to handle it (i.e. ErrorBoundary)
  • In the first example, there was no clean up of the promise.
    • Let's say param was 1 then became 2 before expensiveAsyncFunction(1) could complete. You'll call setData twice, but you don't know in what order.
    • This is now handled by React.

Main use cases

When you have something asynchronous you need to fetch because of a state change, i.e. a search query.

Otherwise, you're better off caching the promise at the module-level.