Using Valtio for Component State
While Valtio is excellent for global state, it can also be used to manage complex component-level state. This is particularly useful when you need to share state between nested components without prop drilling or when you want the benefits of Valtio's reactivity for a state that is tied to a component's lifecycle.
To achieve this, you can create a proxy
instance inside a useRef
to ensure the state persists across re-renders without being recreated.
Using useRef
and React.Context
A common pattern is to create the proxy state within a component and provide it to its children via React Context.
import { createContext, useContext, useRef } from 'react';
import { proxy, useSnapshot } from 'valtio';
// 1. Create a context
const MyContext = createContext();
// 2. Create a provider component that initializes the state
const MyProvider = ({ children }) => {
// Create the proxy state inside a useRef to persist it
const state = useRef(proxy({ count: 0 })).current;
return <MyContext.Provider value={state}>{children}</MyContext.Provider>;
};
// 3. Create a consumer component
const MyCounter = () => {
// Get the state from the context
const state = useContext(MyContext);
const snap = useSnapshot(state);
return (
<>
Count: {snap.count}
<button onClick={() => ++state.count}>+1</button>
</>
);
};
// 4. Wrap your components with the provider
function App() {
return (
<MyProvider>
<MyCounter />
</MyProvider>
);
}
This pattern confines the Valtio state to the component subtree wrapped by MyProvider
, making MyCounter
and any other consumers reusable and self-contained.
Alternatives to useRef
If you prefer not to use useRef
directly, several community libraries offer elegant alternatives:
- use-constant: A simple hook to create a constant value for the lifetime of a component.
- bunshi: A library for creating scoped, injectable atoms that works well with Valtio.
Bunshi Example
Bunshi provides a clean way to define and inject state without manually managing context.
// molecules.ts
import { molecule } from 'bunshi';
import { proxy } from 'valtio';
export const CounterState = molecule(() => proxy({ count: 0 }));
// Component.tsx
import { useMolecule } from 'bunshi/react';
import { useSnapshot } from 'valtio';
import { CounterState } from './molecules';
function Counter() {
const state = useMolecule(CounterState);
const snap = useSnapshot(state);
// ...
}
CodeSandbox Example
See a live example of using Valtio for component-level state with useRef
and Context.
https://codesandbox.io/s/valtio-component-ye5tbg?file=/src/App.tsx