How to: persist a valtio state

Why valtio?

JavaScript ecosystem has quite a few choices for state management. Redux, Mobx, VueX, Recoil, Hookstate, Valtio… the list seems endless. Valtio is a relatively new way of managing state in this list, and it uses JavaScript proxies to provide reactivity.

A typical redux based setup has the following:

  • One global store
  • Multiple action creators
  • Many reducer functions
  • Thunks/sagas to handle async operations
  • You may also use reselect to create computed values
  • Working with immutable data may need additional care

This setup quickly gets verbose and the single global store grows quite large.

In contrast, you can have the following setup in valtio:

  • Multiple state objects based on your app’s features
  • Actions are plain functions which directly modify the state. No reducers required. Since these can be async functions, you don’t necessarily need additional abstractions to handle async operations

Thus, developing with valtio is a lot simpler than redux, and that’s why you should consider valtio.

Local persistence

Redux has a popular solution for this. redux-persist library is used to store the state to localStorage on web or AsyncStorage on react native. As of now, no such library exists for valtio. However, it’s fairly straightforward to implement yourself with valtio subscriptions.

Subscriptions allow you to access the state from anywhere, and do something when state changes. This is a good place to persist the state to local storage.

Let’s begin by creating a valtio state.

import { proxy } from 'valtio';

const state = proxy({ todos: [] });

Next, let’s subscribe to the state changes, and save to localStorage.

subscribe(state, () => {
localStorage.set('todosState', JSON.stringify(state));

The state is now persisted to local storage any time the state changes, but we are not hydrating the state anywhere. It will be a blank list again if the user refreshes/starts a new session.

Simplest way to solve this is to parse the stored data and provide it as initial value to the state.

const storedStateString = localStorage.getItem('todosState');
const initialState = storedStateString ? JSON.parse(storedStateString) : { todos: [] };

The final solution:

// states/todos.js
import { proxy } from 'valtio';
const storedStateString = localStorage.getItem('todosState');
const initialState = storedStateString ? JSON.parse(storedStateString) : { todos: [] };
const state = proxy(initialState);subscribe(state, () => {
localStorage.set('todosState', JSON.stringify(state));
export default state;

React Native & AsyncStorage

While the solution above is great for web, it doesn’t work for React Native where you need to use AsyncStorage. The hydration will have to happen asynchronously, and thus you need to defer the usage of the state until it is hydrated.

// states/todos.js
import AsyncStorage from '@react-native-async-storage/async-storage';
import { proxy } from 'valtio';

const state = proxy({ todos: [], hydrated: false });
subscribe(state, () => {
AsyncStorage.setItem('todosState', JSON.stringify(state));
export const hydrateState = async () => {
const storedStateString = await AsyncStorage.getItem('todosState');
state.todos = JSON.parse(storedStateString).todos;
state.hydrated = true;
export default state;// App.tsx
import { useSnapshot } from 'valtio';
import React, { useEffect } from 'react';
import state, { hydrateState } from './states/todos';
const App = () => {
const { hydrated, todos } = useSnapshot(state);
useEffect(() => {
}, []);
// Return your actual component which renders the app
// return a loading screen

Closing thoughts

While local persistence is great, you need take care of data migrations and cache invalidations. In any significantly complex app, you will have multiple states which you need to organise, persist, and hydrate. Expect more posts on these topics soon!




You may check my website for my bio, at

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Harness the power of Draft.js

Sitecore Javascript Services (JSS)

Strongly Keyed Maps in TypeScript

Quarantimer — A timer to get your quarantine into perspective

Navisworks 2017.2 Update out today

Cross-Platform Apps With Electron and React: Part 3

Building Test Pyramid in React

How to Pass Props in Vue 3 with Composition API

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Prabhakar Bhat

Prabhakar Bhat

You may check my website for my bio, at

More from Medium

Do not pollute your app root

My config folder

How can you deliver a variety of images in different formats on a high-traffic site in real time?

Example of the same image in different size on the site

Give your users more freedom with data grid row grouping

How to set a global timeout for @testing-library async methods