A Guide to Efficient State Management with React Query and Zustand

0

In modern React applications, efficiently managing server state and client state is a crucial task. Libraries such as React Query and Zustand can be used to address this challenge. This article will walk you through the steps of using React Query and Zustand to handle state management in a simple and intuitive manner.

1. Introduction and Usage of React Query

React Query is a library that helps manage server state effortlessly. It simplifies tasks like data fetching, caching, synchronization, and server state updates.

Installation

npm install @tanstack/react-query
npm install @tanstack/react-query-devtools

Setup

First, configure the QueryClient and wrap your application with QueryClientProvider.

import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* Your app components go here */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

export default App;

Data Fetching

Use the `useQuery` hook to fetch data.

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

const fetchUsers = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
  return data;
};

function Users() {
  const { data, error, isLoading } = useQuery(['users'], fetchUsers);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map(user => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

export default Users;

Using Mutations

Use the `useMutation` hook for data modification or deletion tasks.

import React from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

const addUser = async (newUser) => {
  const { data } = await axios.post('https://jsonplaceholder.typicode.com/users', newUser);
  return data;
};

function AddUser() {
  const queryClient = useQueryClient();
  const mutation = useMutation(addUser, {
    onSuccess: () => {
      queryClient.invalidateQueries(['users']);
    },
  });

  const handleAddUser = () => {
    mutation.mutate({ name: 'New User', email: 'newuser@example.com' });
  };

  return (
    <div>
      <button onClick={handleAddUser}>
        {mutation.isLoading ? 'Adding...' : 'Add User'}
      </button>
      {mutation.isError && <div>Error: {mutation.error.message}</div>}
      {mutation.isSuccess && <div>User added!</div>}
    </div>
  );
}

export default AddUser;

2. Introduction and Usage of Zustand

Zustand is a state management library that helps manage state in React applications simply and intuitively.

Installation

npm install zustand

Creating a State Store

import create from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
}));

export default useStore;

Using State

import React from 'react';
import useStore from './store';

function Counter() {
  const { count, increase, decrease } = useStore();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
    </div>
  );
}

export default Counter;

Using Middleware

An example of saving state changes to local storage.

import create from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(persist(
  (set) => ({
    count: 0,
    increase: () => set((state) => ({ count: state.count + 1 })),
    decrease: () => set((state) => ({ count: state.count - 1 })),
  }),
  {
    name: 'counter-storage',
  }
));

export default useStore;

Using DevTools

Integration with Redux DevTools is supported.

import create from 'zustand';
import { devtools } from 'zustand/middleware';

const useStore = create(devtools((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
})));

export default useStore;

3. Full Example Code

A complete example of using React Query and Zustand together to manage state.

import React from 'react';
import create from 'zustand';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import axios from 'axios';

const queryClient = new QueryClient();

const useStore = create((set) => ({
  count: 0,
  name: 'Zustand',
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
  setName: (newName) => set(() => ({ name: newName })),
}));

const fetchUsers = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
  return data;
};

function Users() {
  const { data, error, isLoading } = useQuery(['users'], fetchUsers);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map(user => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

function Counter() {
  const { count, name, increase, decrease, setName } = useStore();

  return (
    <div>
      <h1>{count}</h1>
      <h2>{name}</h2>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
      <button onClick={() => setName('New Zustand Name')}>Change Name</button>
    </div>
  );
}

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Counter />
      <Users />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

export default App;

4. Best Practices for State Management and Conclusion

  • Simple State Models: Simplify state models and manage the application with as few states as possible.
  • Separation of Concerns: Separate state management logic from component logic to improve maintainability.
  • Efficient Data Fetching: Fetch data only when necessary and use data caching to optimize performance.
  • Asynchronous Task Management: Properly handle asynchronous tasks to maintain UI consistency.

By using React Query and Zustand together, you can efficiently manage server and client state, enhancing the performance and maintainability of your React applications. Now, try applying this guide to your projects!

Leave a Reply