Skip to content

Quick Start

React Concurrent Store is a ponyfill that brings the experimental React concurrent store API to React 19+. It allows you to manage async resources with proper handling for non-blocking updates.

  1. Install the package

    Terminal window
    npm install https://pkg.pr.new/thejustinwalsh/react-concurrent-store@7e8861d
  2. Import the hooks

    import { createStore, useStore } from "react-concurrent-store";
  3. Create your first store

    // Create a simple store with initial data
    const counterStore = createStore(0);
    // Or create a store with async data
    const fetchUser = async (id: number) => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
    };
    const userStore = createStore(fetchUser(1));
  4. Use the store in your components

    import { Suspense, use } from "react";
    function Counter() {
    const count = useStore(counterStore);
    return (
    <div>
    <p>Count: {count}</p>
    <button onClick={() => counterStore.update(count + 1)}>
    Increment
    </button>
    </div>
    );
    }
    function UserProfile() {
    const userPromise = useStore(userStore);
    const user = use(userPromise); // Suspend until resolved
    return (
    <div>
    <h1>{user.name}</h1>
    <p>{user.email}</p>
    </div>
    );
    }
    function App() {
    return (
    <div>
    <Counter />
    <Suspense fallback={<div>Loading user...</div>}>
    <UserProfile />
    </Suspense>
    </div>
    );
    }

Stores are containers that hold values or promises. They can be updated reactively and integrate seamlessly with React’s concurrent features.

Use useTransition to wrap the update action in a transition and track the action’s isPending state. The store will always execute the update in an action, even if the update is not wrapped in a transition.

import { useTransition } from "react";
function UserProfile() {
const userPromise = useStore(userStore);
const user = use(userPromise);
const [isPending, startTransition] = useTransition();
const loadNextUser = () => {
startTransition(() => {
userStore.update(fetchUser(user.id + 1));
});
};
return (
<div style={{ opacity: isPending ? 0.5 : 1 }}>
<h1>{user.name}</h1>
<button onClick={loadNextUser}>Next User</button>
</div>
);
}

Stores containing promises automatically integrate with React Suspense by passing the store result to the use hook. When the store is updated as an Action within a non-blocking Transition, the store will properly update the state without de-opting to a synchronous update.