Leonardo Maldonado Fullstack developer. JavaScript, React, TypeScript, GraphQL.

Simplify proxy state with Valtio

5 min read 1552

Valtio

State management in React is a problem that developers sometimes over engineer. There is always some new library and choosing the right library for your application can be a pretty difficult job.

It’s a crucial thing for every modern application to have a state-management library and there’s a lot of points that should be taken into consideration when choosing a library.

We’re going to work with a new state-management library called Valtio, a library that makes proxy-state simple for JavaScript and React applications.

What is a proxy?

Proxies are a computer software pattern. Proxies are a wrapper around some objects that can have custom behavior. We can proxy almost anything such as network connection, objects, files, etc. Proxies do not work the same way but are similar in structure to adapters and decorators.

A proxy is a wrapper object that is being called by the client to access the real serving object behind the scenes

Proxies are part of the GoF design patterns, a book written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. The authors explore the capabilities of object-oriented programming and 23 software design patterns.

Proxies can help developers to solve recurring problems in modern applications. Proxies are easier because they help us with objects, they can be very helpful for situations such as validation, tracing property accesses, web services, monitoring objects, etc. They are objects that are easier to put in place, change, test, and reuse.

Proxies have two rules:

  • The access to an object should be controlled
  • Extra functionality should be provided when accessing an object

Since the ES6 version, we have proxies available in JavaScript. A proxy receives two parameters:

  • target – the original object that you want to proxy
  • handler – the object that will define operations

This is how we can create a simple proxy using JavaScript:

const target = {
  addition: () => 2 + 2,
  subtraction: () => 2 - 2,
};

const handler = {
  get: function(target, prop, receiver) {
    return target;
  },
};

const proxy = new Proxy(target, handler);

console.log(proxy.addition()); // 4
console.log(proxy.subtraction()); // 0

Proxies are a very powerful design pattern for observing an object and making a change to it. It allows us to design custom behavior for objects.

We made a custom demo for .
No really. Click here to check it out.

Imagine if we could do this in a React application. Making use of proxies for handling our state data. We no longer need to set up huge state-management libraries for handling all our state. With Valtio, we can!

Valtio

Valtio is a library that makes proxy state simple for React and JavaScript applications.

It was created by an open-source collective called Poimandres. This open-source collective is responsible for other important libraries in the React community, such as react-spring, zustand, react-three-fiber, and react-use-gesture.

What we need to do is wrap our state object and then we can mutate it anywhere in our application:

import { proxy } from 'valtio'

const state = proxy({ count: 0 });
() => { ++state.count };

Valtio has a hook called useProxy that helps us to read from snapshots. The useProxy hook will only rerender our component when the part of the state that the component is accessing has changed:

const Counter = () => {
  const snapshot = useProxy(state);
  return (
    <div>
      {snapshot.count}
      <button onClick={() => ++state.count}>Add</button>
    </div>
  );
};

Valtio has a really powerful function called subscribe. We can subscribe to our state from anywhere and use it in our components.

Imagine that we have a very complex proxy state with a bunch of different states. Inside our proxy state, we have an authenticated state, which we use to know when the user is authenticated or not:

import { proxy } from 'valtio'

const state = proxy({
  authenticated: false,
  settings: { ... },
  filters: { ... },
  ...
});

We can subscribe to a specific part of our state using the subscribe function. The subscribe function accepts two parameters, state and callback. The state is which part of our state we want to subscribe. The callback is a callback function that will be triggered when the state has changed:

subscribe(state.authenticated, () => console.log('State changed to', state.authenticated));

Valtio also has a function called subscribeKey that is like the subscribe function. The subscribeKey will subscribe to a primitive property of a state proxy. It will only be fired when that specified property has changed though:

subscribeKey(state.authenticated, () => console.log('Authenticated state changed to', state.authenticated));

While vanilla JavaScript may not be as common these days, another feature of Valtio is it can be used in vanilla JavaScript applications:

import { proxy, subscribe, snapshot } from 'valtio/vanilla'

const state = proxy({ books: [...], isAuthenticated: false })

subscribe(state, () => {
  console.log('state:')
  const obj = snapshot(state);
})

Now that we know a little bit about Valtio, let’s see how it works in practice. We will create a simple example application using Valtio for a state proxy in React and see the benefits of it.

Getting started

We will create a new application using Create React App:

npx create-react-app simple-state-with-valtio

Now we will install Valtio:

yarn add valtio

First, we are going to import the proxy and useProxy the function from Valtio. The proxy function is used for creating a new proxy state. The useProxy function is used for creating a local snapshot on our React component that watches for changes:

import { proxy, useProxy } from 'valtio'

Now, we are going to create our proxy state. We are going to have three properties inside our state firstName, lastName, and users:

const state = proxy({ firstName: "", lastName: "", users: [{}] })

Inside our component, we are going to have a form, two inputs, and one button. We are going to track changes for each input and store it inside our state proxy. The button is going to be used for submitting our form:

const App = () => {
  return (
    <form>
      <input type="text" />
      <input type="text" />
      <button type="submit">Create</button>
    </form>
    );
}

Inside our component, we are going to create a snapshot using the useProxy hook. This snapshot will be used for getting our state data from our proxy state and update it whenever the state changes:

const App = () => {
  const snapshot = useProxy(state, { sync: true });
  return (
    <form>
      <input type="text" />
      <input type="text" />
      <button type="submit">Create</button>
    </form>
  );
}

Notice that we passed a second argument to the useProxy function. A second argument is an object and we’re telling Valtio to batch before triggering a rerender.

Now on our inputs, for each input, we are going to set the value to our proxy state and read the value from our snapshot, like this:

const App = () => {
  const snapshot = useProxy(state, { sync: true });
return (
    <form>
      <input type="text" value={snapshot.firstName} onChange={(e) => (state.firstName = e.target.value)} />
      <input type="text" value={snapshot.lastName} onChange={(e) => (state.lastName = e.target.value)} />
      <button type="submit">Create</button>
    </form>
  );
}

Now, we need to create our submit function to submit our form. Inside our function, we are going to create a new user grouping both firstName and lastName values and push it to the users array. After the new user is pushed to the users array, we want to change the values of both firstName and lastName to an empty string:

const App = () => {
  const snapshot = useProxy(state, { sync: true });
  const handleSubmit = (e: any) => {
    e.preventDefault();
    const newUser = { firstName: state.firstName, lastName: state.lastName };
    state.users.push(newUser);
    state.firstName = "";
    state.lastName = "";
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={snapshot.firstName} onChange={(e) => (state.firstName = e.target.value)} />
      <input type="text" value={snapshot.lastName} onChange={(e) => (state.lastName = e.target.value)} />
      <button type="submit">Create</button>
    </form>
  );
}

We now have our component working pretty fine. We are able to manage our state value using Valtio and submit new users to our proxy state. The only thing that is missing now is a way to show the number of users we have.

We are going to use the snapshot one more time and map over our users’ array and for each user we are going to show an h1 element:

const App = () => {
  const snapshot = useProxy(state, { sync: true });
const handleSubmit = (e: any) => {
    e.preventDefault();
    const newUser = { firstName: state.firstName, lastName: state.lastName };
    state.users.push(newUser);
    state.firstName = "";
    state.lastName = "";
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={snapshot.firstName} onChange={(e) => (state.firstName = e.target.value)} />
      <input type="text" value={snapshot.lastName} onChange={(e) => (state.lastName = e.target.value)} />
      <button type="submit">Create</button>

      <div>
        {snapshot.users.map((user: any) => (<h1>Hello {user.firstName} {user.lastName}</h1>))}
      </div>
    </form>
  );
}

The collective behind Valtio is very important and respected in the React community. They’re also authors of important projects such as zustand, react-spring, react-three-fiber, etc.

The future is very bright for the Valtio library, with a collective and developers willing to contribute to the project.

Conclusion

State data is essential for modern applications. Valtio is simple, powerful, and combines the power of proxies in React and JavaScript making state data easy to be used and changed as well.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Leonardo Maldonado Fullstack developer. JavaScript, React, TypeScript, GraphQL.

Leave a Reply