DEV Community

Cover image for Simplifying State Management in React with Valtio
jamel-86
jamel-86

Posted on

Simplifying State Management in React with Valtio

Introduction:

State management in React has often been a complex topic, with various libraries offering different approaches. Valtio, a lesser-known but powerful state management library, takes a unique approach by making your state directly mutable and reactive. This post explores what Valtio is, how it works, and why it might be the right choice for your next project.

What is Valtio?
Valtio is a lightweight state management library that uses JavaScript proxies to make any object reactive. It’s designed to be simple and intuitive, allowing developers to work with state as if it’s a plain JavaScript object.

How Does Valtio Work?
Valtio creates a proxy object for your state. Whenever you mutate this proxy, Valtio automatically tracks the changes and triggers re-renders of components that depend on this state. This eliminates the need for reducers, actions, or stores, making state management more straightforward.

Getting Started with Valtio:

1. Installation:
First, you need to install Valtio via npm:

npm install valtio

2. Creating Reactive State:
Valtio allows you to create a reactive state using the proxy function. Here's how you can do it:

import { proxy } from 'valtio';

const state = proxy({
  count: 0,
  text: 'Hello, Valtio!',
});

function increment() {
  state.count += 1;
}
Enter fullscreen mode Exit fullscreen mode

3. Using State in Components:
Valtio provides a useSnapshot hook that you can use to connect your state to React components:

import React from 'react';
import { useSnapshot } from 'valtio';

function Counter() {
  const snap = useSnapshot(state); // Read the state snapshot

  return (
    <div>
      <p>{snap.text}</p>
      <p>Count: {snap.count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the Counter component will automatically re-render whenever state.count or state.text changes.

Where Should You Set Up Proxies?

Valtio proxies are flexible and can be set up anywhere in your application depending on your needs:

  • Global State (Centralized Management): If the state is used across many parts of your application, it’s often beneficial to set up the proxy in a centralized location, such as a store.js file. This allows you to import and use the state wherever it's needed.

Example:

// store.js
import { proxy } from 'valtio';

export const state = proxy({
  user: null,
  theme: 'light',
});
Enter fullscreen mode Exit fullscreen mode

Then, you can use this state across your application:

import { useSnapshot } from 'valtio';
import { state } from './store';

function App() {
  const snap = useSnapshot(state);
  return (
    <div>
      <p>Current Theme: {snap.theme}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Local State (Component-Specific Management):
For state that is only relevant to a single component, you can create the proxy directly within that component. This approach keeps the state management localized and the component self-contained.

Example:

Import React from 'react';
import { proxy, useSnapshot } from 'valtio';

function Counter() {
  const localState = proxy({
    count: 0,
  });

  const snap = useSnapshot(localState);

  return (
    <div>
      <p>Count: {snap.count}</p>
      <button onClick={() => (localState.count += 1)}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Does Valtio Replace useEffect?

While useSnapshot handles state reactivity, useEffect is still crucial for managing side effects in your components. Here’s an example where both are used together:

import React, { useEffect } from 'react';
import { proxy, useSnapshot } from 'valtio';

const state = proxy({
  count: 0,
});

function increment() {
  state.count += 1;
}

function Counter() {
  const snap = useSnapshot(state);

  useEffect(() => {
    console.log('Component mounted');
    // Perform some side effect here, like fetching data
    return () => {
      console.log('Component unmounted');
      // Cleanup side effect here
    };
  }, []); // Empty dependency array means it runs once on mount

  return (
    <div>
      <p>Count: {snap.count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Advanced Usage: Derived State

Valtio supports derived state, which is essentially computed state that depends on the reactive state. Here’s an example:

import { proxy, useSnapshot } from 'valtio';

const state = proxy({
  count: 0,
  doubleCount: () => state.count * 2, // Derived state
});

function increment() {
  state.count += 1;
}

function Counter() {
  const snap = useSnapshot(state);
  return (
    <div>
      <p>Count: {snap.count}</p>
      <p>Double Count: {snap.doubleCount}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Best Practices with Valtio

  • Keep It Simple: Valtio is designed to minimize boilerplate. Take advantage of direct state mutations without worrying about complex actions or reducers.

  • Use useEffect When Needed: Although Valtio simplifies state management, useEffect is still necessary for handling side effects like data fetching or subscriptions.

  • Derived State for Computations: Use derived state to compute values that depend on other state properties without having to store redundant data.
    Conclusion:

Valtio offers a refreshing approach to state management by allowing direct state mutations and handling reactivity behind the scenes. It’s an excellent choice for developers looking for simplicity without sacrificing performance. If you’re tired of boilerplate-heavy state management solutions, Valtio might be exactly what you need.

If you found this useful, please give it a like! and feel free to share your thoughts in the comments!

Top comments (0)