← Back to list

Valtio: Unveiling the Silent Management Ninja: Where the plot thickens

Part 2: Where the plot thickens

Guhaprasaanth Nandagopal in Stackademic · 2024-07-11 14:29 · 18 claps · 12.0 min read paywalled
#reactjs #nextjs #valtio #server-side-rendering #proxy
Open on Medium ↗
Wiki topics: BIZ · Business Strategy 🌐 · Web Development

Valtio: Unveiling the Silent Management Ninja

Part 2: Where the plot thickens

Handling Asynchronous State Updates with Valtio

Valtio is a powerful state management library for React that utilizes JavaScript Proxies to create reactive state objects. One of the essential aspects of modern web development is managing asynchronous operations, such as fetching data from an API. This article explores how Valtio handles asynchronous state updates, providing a comprehensive guide with sample code.

Introduction to Asynchronous State Updates

Asynchronous operations like data fetching, updating, and deleting are common in a React application. Managing these operations efficiently and ensuring the UI reflects the application’s current state can be challenging. Valtio simplifies this process by making state management reactive and straightforward.

Handling Asynchronous State Updates

Valtio can handle asynchronous updates by directly modifying the state within asynchronous functions. The reactive nature of Valtio ensures that any changes to the state automatically trigger re-renders of the components using that state.

Fetching Data from an API

Example:

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

const state = proxy({
  data: null,
  loading: false,
  error: null,
  fetchData: async () => {
    state.loading = true;
    state.error = null;
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      state.data = result;
    } catch (error) {
      state.error = error.message;
    } finally {
      state.loading = false;
    }
  }
});
export default state;

Using the State in a Component

Example:

// DataComponent.js
import React, { useEffect } from 'react';
import { useSnapshot } from 'valtio';
import state from './state';

const DataComponent = () => {
  const snap = useSnapshot(state);
  useEffect(() => {
    state.fetchData();
  }, []);
  if (snap.loading) return <div>Loading...</div>;
  if (snap.error) return <div>Error: {snap.error}</div>;
  return (
    <div>
      <h1>Data</h1>
      <pre>{JSON.stringify(snap.data, null, 2)}</pre>
    </div>
  );
};
export default DataComponent;

Handling Multiple Asynchronous Operations

In more complex applications, you might need to handle multiple asynchronous operations. Valtio’s reactivity makes it straightforward to manage such scenarios by updating the state as needed.

Example:

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

const state = proxy({
  users: [],
  posts: [],
  loadingUsers: false,
  loadingPosts: false,
  errorUsers: null,
  errorPosts: null,
  fetchUsers: async () => {
    state.loadingUsers = true;
    state.errorUsers = null;
    try {
      const response = await fetch('https://api.example.com/users');
      const result = await response.json();
      state.users = result;
    } catch (error) {
      state.errorUsers = error.message;
    } finally {
      state.loadingUsers = false;
    }
  },
  fetchPosts: async () => {
    state.loadingPosts = true;
    state.errorPosts = null;
    try {
      const response = await fetch('https://api.example.com/posts');
      const result = await response.json();
      state.posts = result;
    } catch (error) {
      state.errorPosts = error.message;
    } finally {
      state.loadingPosts = false;
    }
  }
});
export default state;

Using the State in a Component:

// MultiDataComponent.js
import React, { useEffect } from 'react';
import { useSnapshot } from 'valtio';
import state from './state';

const MultiDataComponent = () => {
  const snap = useSnapshot(state);
  useEffect(() => {
    state.fetchUsers();
    state.fetchPosts();
  }, []);
  return (
    <div>
      <h1>Users and Posts</h1>
      <section>
        <h2>Users</h2>
        {snap.loadingUsers ? (
          <div>Loading...</div>
        ) : snap.errorUsers ? (
          <div>Error: {snap.errorUsers}</div>
        ) : (
          <pre>{JSON.stringify(snap.users, null, 2)}</pre>
        )}
      </section>
      <section>
        <h2>Posts</h2>
        {snap.loadingPosts ? (
          <div>Loading...</div>
        ) : snap.errorPosts ? (
          <div>Error: {snap.errorPosts}</div>
        ) : (
          <pre>{JSON.stringify(snap.posts, null, 2)}</pre>
        )}
      </section>
    </div>
  );
};
export default MultiDataComponent;

Unit Testing a React App with Valtio Using React Testing Library

Unit testing is a crucial aspect of building reliable and maintainable React applications. Valtio, a reactive state management library for React, provides an intuitive way to manage state with automatic reactivity using JavaScript Proxies. This article will guide you through unit testing a React app that uses Valtio for state management, utilizing React Testing Library and Jest.

Prerequisites

Before we begin, ensure you have the necessary libraries installed:

npm install valtio @testing-library/react @testing-library/jest-dom jest

Testing Asynchronous State Updates

If your Valtio state involves asynchronous updates, you can handle these in your tests using async/await.

asyncState.js:

import { proxy } from 'valtio';

const asyncState = proxy({
  data: null,
  loading: false,
  error: null,
  async fetchData() {
    this.loading = true;
    this.error = null;
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const result = await response.json();
      this.data = result;
    } catch (error) {
      this.error = error.message;
    } finally {
      this.loading = false;
    }
  }
});
export default asyncState;

AsyncComponent.js:

import React, { useEffect } from 'react';
import { useSnapshot } from 'valtio';
import asyncState from './asyncState';
const AsyncComponent = () => {
  const snap = useSnapshot(asyncState);
  useEffect(() => {
    asyncState.fetchData();
  }, []);
  if (snap.loading) return <div>Loading...</div>;
  if (snap.error) return <div>Error: {snap.error}</div>;
  if (!snap.data) return null;
  return (
    <div>
      <p>Data: {snap.data.title}</p>
    </div>
  );
};
export default AsyncComponent;

AsyncComponent.test.js:

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import AsyncComponent from './AsyncComponent';
import asyncState from './asyncState';

// Mock fetch
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ title: 'delectus aut autem' }),
  })
);
describe('AsyncComponent', () => {
  beforeEach(() => {
    asyncState.data = null;
    asyncState.loading = false;
    asyncState.error = null;
  });
  test('fetches and displays data', async () => {
    render(<AsyncComponent />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
    await waitFor(() => expect(screen.getByText('Data: delectus aut autem')).toBeInTheDocument());
    expect(fetch).toHaveBeenCalledTimes(1);
  });
  test('handles fetch error', async () => {
    fetch.mockImplementationOnce(() => Promise.reject(new Error('Failed to fetch')));
    render(<AsyncComponent />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
    await waitFor(() => expect(screen.getByText('Error: Failed to fetch')).toBeInTheDocument());
  });
});

Testing Derived State

Valtio supports a derived state, which can be tested similarly to a regular state.

derivedState.js:

import { proxy } from 'valtio';
import { derive } from 'valtio/utils';

const state = proxy({
  count: 0,
  increment() {
    this.count++;
  },
  decrement() {
    this.count--;
  },
});
const derivedState = derive({
  doubleCount: (get) => get(state).count * 2,
});
export { state, derivedState };

DerivedComponent.js:

import React from 'react';
import { useSnapshot } from 'valtio';
import { state, derivedState } from './derivedState';

const DerivedComponent = () => {
  const snap = useSnapshot(state);
  const derivedSnap = useSnapshot(derivedState);
  return (
    <div>
      <p>Count: {snap.count}</p>
      <p>Double Count: {derivedSnap.doubleCount}</p>
      <button onClick={() => state.increment()}>Increment</button>
      <button onClick={() => state.decrement()}>Decrement</button>
    </div>
  );
};
export default DerivedComponent;

DerivedComponent.test.js:

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import DerivedComponent from './DerivedComponent';
import { state } from './derivedState';

describe('DerivedComponent', () => {
  beforeEach(() => {
    state.count = 0; // Reset state before each test
  });
  test('renders initial count and double count', () => {
    render(<DerivedComponent />);
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
    expect(screen.getByText('Double Count: 0')).toBeInTheDocument();
  });
  test('updates count and double count on increment', () => {
    render(<DerivedComponent />);
    fireEvent.click(screen.getByText('Increment'));
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
    expect(screen.getByText('Double Count: 2')).toBeInTheDocument();
  });
  test('updates count and double count on decrement', () => {
    render(<DerivedComponent />);
    fireEvent.click(screen.getByText('Decrement'));
    expect(screen.getByText('Count: -1')).toBeInTheDocument();
    expect(screen.getByText('Double Count: -2')).toBeInTheDocument();
  });
});

Persisting State with Valtio

Persisting state across sessions in Valtio can enhance user experience by maintaining user data or preferences across browser sessions. To achieve this, you can integrate Valtio with browser storage APIs like localStorage or sessionStorage.

Here’s how you can persist the state with Valtio:

Step 1: Create a Utility to Sync State with Storage

import { proxy, subscribe } from 'valtio';
const loadState = () => {
  const savedState = localStorage.getItem('valtio_state');
  return savedState ? JSON.parse(savedState) : { count: 0 };
};

const savedState = loadState();
const state = proxy(savedState);

subscribe(state, () => {
  localStorage.setItem('valtio_state', JSON.stringify(state));
});

Step 2: Use the State in Your Application

function Counter() {
  const snapshot = useSnapshot(state);
  return (
    <div>
      <p>{snapshot.count}</p>
      <button onClick={() => ++state.count}>Increment</button>
    </div>
  );
}

Considerations and Best Practices

  • Performance: While reading from and writing to localStorage can be synchronous and block rendering, for large amounts of data, consider debouncing the storage updates or moving them into a Web Worker.
  • Security: Be cautious about what you store in localStorage as it's accessible from client scripts and can be vulnerable to XSS attacks.
  • Complex State Structures: For more complex state structures, consider serializing parts of the state selectively to avoid performance bottlenecks.

Valtio and Server-Side Rendering (SSR) in React Applications

Server-Side Rendering (SSR) is a popular technique in modern web development that enables rendering React components on the server before sending HTML to the client. This improves initial load times and SEO, providing a better user experience. Valtio, a reactive state management library for React, can be integrated with SSR to manage state efficiently both on the server and client sides. In this article, we will explore how Valtio can support SSR in a React application, including a detailed setup and code examples.

Understanding SSR and Its Benefits

What is SSR?

SSR is the process of rendering a web page on the server instead of in the browser. The server generates the HTML for the page and sends it to the client, where the browser can then hydrate the page, making it fully interactive with JavaScript.

Benefits of SSR

  1. Improved Performance: Faster initial page load times because the HTML is rendered on the server.
  2. Better SEO: Search engines can crawl and index the fully rendered HTML content.
  3. Enhanced User Experience: Users see the fully rendered content more quickly, reducing the perceived load time.

Integrating Valtio with SSR

Valtio can be seamlessly integrated into a React application that uses SSR. The main challenge with SSR and state management is ensuring that the server-rendered state is properly transferred to the client for hydration.

Setting Up Valtio with SSR

Step 1: Install Dependencies

First, ensure you have the necessary dependencies installed:

npm install valtio react react-dom express

Step 2: Define the Valtio State

Create a Valtio state that will be used on both the server and client.

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

const state = proxy({
  count: 0,
  increment() {
    this.count++;
  },
  decrement() {
    this.count--;
  },
});
export default state;

Step 3: Create React Components

Create a React component that uses the Valtio state.

// Counter.js
import React from 'react';
import { useSnapshot } from 'valtio';
import state from './state';

const Counter = () => {
  const snap = useSnapshot(state);
  return (
    <div>
      <h1>SSR with Valtio</h1>
      <p>Count: {snap.count}</p>
      <button onClick={() => state.increment()}>Increment</button>
      <button onClick={() => state.decrement()}>Decrement</button>
    </div>
  );
};
export default Counter;

Step 4: Set Up Server-Side Rendering

Set up an Express server to handle SSR with React and Valtio. Create an index.js file in the project root:

// server.js
import express from 'express';
import { renderToString } from 'react-dom/server';
import { proxy, snapshot } from 'valtio';
import App from './App'; // Your root React component
import state from './state';

const app = express();
app.use(express.static('public'));
app.get('*', (req, res) => {
  // Create a snapshot of the Valtio state
  const stateSnapshot = snapshot(state);
  // Render the React app to a string
  const html = renderToString(<App />);
  // Inject the state snapshot into the HTML
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR with Valtio</title>
        <script>
          window.__VALTIO_STATE__ = ${JSON.stringify(stateSnapshot)};
        </script>
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});
app.listen(3000, () => {
  console.log('Server is listening on port 3000');
});

Step 5: Hydrate the App on the Client

On the client side, hydrate the app and restore the Valtio state from the server-rendered state.

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { StoreProvider, store } from './store';
import App from './App';

const initialState = window.__INITIAL_STATE__;
Object.assign(store, initialState);

ReactDOM.hydrate(
  <StoreProvider value={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StoreProvider>,
  document.getElementById('root')
);

Step 6: Build and Run the Application

  1. Build the React application:
npm run build
  1. Start the Express server
node index.js

Visit http://localhost:3000 to see your server-side rendered React application using Valtio for state management.

Pros of Using Valtio with React SSR

1. Simplified State Management

  • Ease of Use: Valtio simplifies state management in React applications by allowing developers to use mutable JavaScript objects while automatically updating the UI in response to state changes.
  • Direct Mutations: Unlike Redux or Context API, Valtio allows direct mutation of the state object, which can simplify the code and make it more intuitive, especially for developers less familiar with Redux’s immutability constraints.

2. Reactive State with Minimal Boilerplate

  • Minimal Boilerplate: Valtio reduces the need for boilerplate code typically associated with other state management libraries. This can lead to cleaner and more maintainable codebases.
  • Automatic Updates: Components automatically re-render when the state changes, which is handled through Valtio’s proxy-based system, ensuring that the UI is always up-to-date with the latest state.

3. Seamless Integration with React

  • Easy Integration: Valtio can be integrated into an existing React project with minimal configuration, making it easy to adopt in both new and existing applications.
  • Flexibility: Works well with other libraries and can complement existing state management solutions, providing flexibility in how state is handled across different parts of the application.

Cons of Using Valtio with React SSR

1. Complexity in State Serialization

  • Serialization Challenges: When using SSR, the state managed by Valtio needs to be serialized into a string when sending it from the server to the client. Due to Valtio’s use of proxies, this can introduce complexities in ensuring that the state is correctly serialized and deserialized without losing reactivity or causing hydration mismatches.
  • Extra Handling Required: Developers need to implement mechanisms to serialize the proxy state and then rehydrate it on the client, maintaining the state’s reactivity and ensuring it syncs accurately between server and client.

2. Potential Performance Overhead

  • Server Load: SSR with state management involves additional computational overhead on the server. Managing reactive state on the server before sending it to the client can increase response times, especially under heavy load or with complex state objects.
  • Hydration Overhead: The client-side JavaScript bundle needs to rehydrate the state sent from the server. If not managed carefully, this can lead to performance bottlenecks, particularly if the initial state is large or complex.

3. Development Complexity

  • Increased Complexity in Debugging: Debugging SSR applications can be more challenging, especially when state mutations need to be tracked across server and client environments.
  • Learning Curve: For teams unfamiliar with SSR or the nuances of state management with proxies, there can be a significant learning curve in understanding how to effectively manage state with Valtio in an SSR setup.

Operational Dynamics of SSR with Valtio:

Proxy-State Management

Valtio uses JavaScript proxies to manage state, which allows for direct state mutations and automatic reactivity. This reduces boilerplate code and simplifies state management.

Server-Side Rendering with React

SSR in React involves rendering components to an HTML string on the server using renderToString from react-dom/server. This pre-rendered HTML is sent to the client, which then hydrates the app to make it interactive.

State Hydration

To ensure the client-side React app starts with the same state as the server-rendered app, the initial state is embedded in the HTML sent to the client. On the client side, this state is used to initialize the Valtio store before hydration.

Static Assets

The Express server serves static assets from the build directory, which includes the bundled JavaScript files created by npm run build.

Understanding SSR in the Context of React and Valtio

SSR in React involves generating the full HTML for a webpage on the server upon a request, which is then sent to the client’s browser. This pre-rendered page is immediately viewable and crawlable by search engines, which is beneficial for SEO and improves the perceived load time of the application. Once the JavaScript bundle necessary for React is downloaded and executed on the client-side, React “hydrates” the static markup to become a fully interactive application.

Valtio, a state management library leveraging JavaScript proxies, offers a straightforward approach by allowing mutable state within React apps. This mutable approach aligns with the natural behavior of JavaScript objects and arrays, simplifying state management by eliminating the usual boilerplate associated with more traditional state management libraries.

Initial Rendering

When a request hits the server, React SSR works by invoking React components to render their output HTML. With Valtio integrated, any state that these components depend on must be initialized right on the server. This state initialization process involves setting up the initial state that your components will use to render. Since Valtio uses proxies to manage and track state changes reactively, initializing this state on the server must ensure all dependencies and proxies are correctly configured to capture the state’s initial conditions.

Challenges with Serialization

A significant challenge in using Valtio with SSR is the serialization of the state. Since Valtio uses ES6 proxies, the state is inherently non-serializable using conventional serialization methods like JSON.stringify(). This issue arises because proxies dynamically intercept and manage access to their target objects, which doesn't translate directly to a static JSON format.

To handle this, developers need to implement custom serialization logic that can de-proxy the state, turning it into a serializable format before sending it to the client. This process typically involves recursively traversing the state, extracting raw data from proxies, and potentially using custom replacers to handle non-serializable values.

State Hydration

Once the HTML and the initial state are sent to the client, the next step is hydrating the application to become interactive. This stage involves initializing the React application on the client with the pre-rendered HTML and then linking it with Valtio’s state. The previously serialized state must be deserialized back into a format that Valtio can use to re-establish proxies and reactive behavior. This step is crucial for ensuring that the client-side application accurately reflects the server-rendered content and can continue functioning with reactive state management.

Considerations for Performance and SEO

While SSR with Valtio enhances SEO by serving fully rendered HTML content, it can impact server performance due to the computational overhead of rendering content and managing state on the server. Optimizing the serialization and hydration processes is crucial to minimizing these performance impacts. Efficiently managing the complexity and size of the state that needs to be serialized can significantly affect the application’s responsiveness and load time.

Conclusion

Valtio provides an intuitive and minimalistic approach to state management in React applications. By leveraging JavaScript proxies, it allows developers to manage state more naturally with mutable objects while still benefiting from the performance optimizations of immutability. Integrating persistence with Valtio is straightforward and can significantly improve the usability of applications by providing a seamless user experience across sessions. Whether you’re building a small project or a large-scale application, Valtio’s simplicity and power make it a worthy addition to your development toolkit. Combining Valtio with SSR in React provides an efficient and straightforward way to manage state and enhance performance. By following this guide, you can set up a React application with server-side rendering and Valtio for state management, resulting in a robust and SEO-friendly web application.

Stackademic 🎓

Thank you for reading until the end. Before you go:


메타데이터
post_id
753f48dff4df
slug
valtio-unveiling-the-state-management-ninja-where-the-plot-thickens-753f48dff4df
url
https://blog.stackademic.com/valtio-unveiling-the-state-management-ninja-where-the-plot-thickens-753f48dff4df
canonical_url
https://blog.stackademic.com/valtio-unveiling-the-state-management-ninja-where-the-plot-thickens-753f48dff4df
author_url
https://medium.com/@guhaprasaanth
status
ok
fetched_at
2026-07-23 10:13:53