Saving data to a global application store is almost inevitably required for any sort of medium to large-sized application. The most popular choice for global state management in React applications is either Redux or the built-in React Context API.
I'd like to present an easy-to-understand alternative: valtio.
I'm going to demonstrate how valtio works through some code examples.
Let's say we've got a global store that holds a todo item when it is fetched from an API. The global store is initialized to a null
value for the todo item.
Here's how the global store would be set up in valtio.
1import { proxy } from "valtio";23type Todo = {4 id: number;5 title: string;6 completed: boolean;7};89const state = proxy<{ todo: Todo | null }>({ todo: null });
Valtio uses JavaScript proxy objects under the hood. This allows users to mutate the global state
object to update data and valtio will have no problem detecting what has changed.
Let's take a look at how a user can set data in the global store based on the result of fetching data through an API call:
1const setTodo = (todo: Todo) => {2 state.todo = todo;3};45// Handler function in a react component...6const handleDataFetching = async () => {7 const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");8 const todo = await res.json();9 setTodo(todo);10};
As you can see, we are directly mutating the global state
when setting the todo item data. This is a noteworthy difference from Redux where all you global state changes should be done in an immutable way for Redux to properly detect when something has changed in a React component.
When you want to use data from the valtio state in a React component, you can use useSnapshot
which will return an immutable "snapshot" of the global state
. Valtio can then detect property access on snapshots since the snapshot is a proxy object. This allows components to only re-render when the parts of the snapshot that are being accessed have changed.
Here is some code for displaying the todo item's title from the global state in the UI:
1// In a React component...2const snap = useSnapshot(state):34return (5 <p>6 <b>Todo title from valtio:</b> {snap.todo.title}7 </p>8)
Anytime the todo item's title gets updated in the global state, the component will re-render since the component is reading from snap.todo.title
. However, if some other parts of the todo
item gets updated, such as the completed
boolean property, the component will not re-render.
Here's a CodeSandbox demo that demonstrates the following:
- fetching of a todo item from an API
- setting the todo item data to the valtio state
- displaying the todo title
- having an input that allows modification of the todo title in the global state
- having a button that toggles the
completed
status of a todo item in the global state - display the render count
A noteworthy thing that should be noticed is that after you fetch the todo data from the API, you can toggle the todo's completed
state all you want and it won't trigger the component to re-render. This is because the component is not reading from snap.todo.completed
and therefore will not cause the component to re-render. Contrast this behavior to that of changing the title in the global state via the text input, where you will notice the render count increment when saving a new title to the global state.
I have yet to use valtio in a large application to manage a large amount of global state, but it's currently my favorite way to manage global state in React apps. Valtio also provides a straightforward way to update and read data from a local store, which is something that isn't all that intuitive with some other state management libraries like Redux.