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.
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:
Since the ES6 version, we have proxies available in JavaScript. A proxy receives two parameters:
target
– the original object that you want to proxyhandler
– the object that will define operationsThis 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.
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 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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.