Async Operations with Valtio
Valtio seamlessly handles promises within its state, making it a powerful tool for managing asynchronous data fetching and operations, especially when combined with React Suspense.
Promises in State
You can assign a Promise
directly as a value in your proxy state. Valtio's snapshot
function (and by extension, useSnapshot
) will automatically handle the promise's lifecycle.
- If the promise is pending, accessing its value in a snapshot will throw the promise, which Suspense can catch.
- If the promise resolves, the snapshot will contain the resolved value.
- If the promise rejects, the snapshot will contain the error, which an Error Boundary can catch.
Here is a vanilla JavaScript example of a self-incrementing counter that uses promises:
import { proxy, subscribe, snapshot } from 'valtio/vanilla';
const store = proxy({
count: new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
});
subscribe(store, async () => {
const value = await snapshot(store).count;
console.log('Count is now:', value);
// Schedule the next increment
store.count = new Promise((resolve) => setTimeout(() => resolve(value + 1), 1000));
});
Integration with React Suspense and use
hook
Valtio is compatible with React 19's use
hook (and shims for React 18), making asynchronous state management declarative and clean.
You can access your async data directly as if it were synchronous, while letting a parent <Suspense>
component handle the loading state.
import { proxy, useSnapshot } from 'valtio';
import { Suspense, use } from 'react'; // React 19
// Create a state with a promise
const state = proxy({
post: fetch('https://api.example.com/post/1').then((res) => res.json())
});
function Post() {
const snap = useSnapshot(state);
// `use(snap.post)` will suspend while the promise is pending
const postData = use(snap.post);
return <div>{postData.title}</div>;
}
function App() {
return (
<Suspense fallback={<span>Loading post...</span>}>
<Post />
</Suspense>
);
}
This approach eliminates the need for manual loading states (isLoading
) and error handling within your component, leading to cleaner and more readable code.
Note: For advanced use cases with
useTransition
, which may be impacted by the "de-opt" nature of this pattern, consider using the use-valtio library.
CodeSandbox Demos
-
Pokemon Fetch Demo: A practical example of fetching data from an API and displaying it with Suspense. https://codesandbox.io/s/valtio-pokemon-fetch-x1lkbj?file=/src/App.tsx
-
Auth Demo: An example showing an async authentication flow. https://codesandbox.io/s/valtio-async-1pstl1?file=/src/App.tsx