Valtio: stupic simple global state management for React

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.

ts
1import { proxy } from "valtio";
2
3type Todo = {
4 id: number;
5 title: string;
6 completed: boolean;
7};
8
9const 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:

ts
1const setTodo = (todo: Todo) => {
2 state.todo = todo;
3};
4
5// 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:

tsx
1// In a React component...
2const snap = useSnapshot(state):
3
4return (
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.



Robert Cooper's big ol' head

Hey, I'm Robert Cooper and I write articles related to web development. If you find these articles interesting, follow me on Twitter to get more bite-sized content related to web development.


Newsletter

Sign up to my newsletter if you like being disappointed.