umma.dev

React Query + Zustand

What is Zustand?

A small light-weight state management library.

Zustand Set Up

npm install zustand

import { create } from "zustand";

const useCounter = create((set) => {
  return {
    counter: 0,
    incrCounter: () => set((state) => ({ counter: state.counter + 1 })),
  };
});
const CounterControl = () => {
  const incrCounter = useCounter((state) => state.incrCounter);

  return (
    <div>
      <button onClick={incrCounter}>Incr. Counter</button>
    </div>
  );
};

What is React-Query?

A way to effectively deal with data management, error handling and caching. Sometimes used as a state management library.

React-Query Set Up

npm install @tanstack/react-query

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";
import { getTodos, postTodo } from "../my-api";

// Create a client
const queryClient = new QueryClient();

function App() {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  );
}

function Todos() {
  // Access the client
  const queryClient = useQueryClient();

  // Queries
  const query = useQuery({ queryKey: ["todos"], queryFn: getTodos });

  // Mutations
  const mutation = useMutation({
    mutationFn: postTodo,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ["todos"] });
    },
  });

  return (
    <div>
      <ul>
        {query.data?.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>

      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: "Do Laundry",
          });
        }}
      >
        Add Todo
      </button>
    </div>
  );
}

render(<App />, document.getElementById("root"));

Why Should You Use Zustand and React-Query?

They work well together. Zustand is easy to pick up if you’re a beginner or if you’re worried about large bundle sizes. React-query has become a go-to with many React applications, particularly if you’re building an SPA rather than an SSR application.

Example of Zustand with React-Query

Create a store with Zustand for state changes.

// src/useStore.js
import create from "zustand";

const useStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  userId: 1,
  setUserId: (userId) => set({ userId }),
}));

export default useStore;

Set up data fetching. This can also be done within a parent component.

// src/api.js
import axios from "axios";

const fetchUser = async ({ queryKey }) => {
  const [, userId] = queryKey;
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  );
  return response.data;
};

export { fetchUser };

Create a component to handle these changes with state.

// src/components/UserButton.js
import React from "react";
import useStore from "../useStore";

const UserButton = ({ id, children }) => {
  const setUserId = useStore((state) => state.setUserId);

  return <button onClick={() => setUserId(id)}>{children}</button>;
};

export default UserButton;
// src/components/UserComponent.js
import React from "react";
import { useQuery } from "react-query";
import useStore from "../useStore";
import { fetchUser } from "../api";

const UserComponent = () => {
  const { user, setUser, userId } = useStore((state) => ({
    user: state.user,
    setUser: state.setUser,
    userId: state.userId,
  }));

  const { data, error, isLoading } = useQuery(["user", userId], fetchUser, {
    onSuccess: (data) => {
      setUser(data);
    },
  });

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

  return (
    <div>
      <h2>User Details</h2>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <p>Phone: {user.phone}</p>
    </div>
  );
};

export default UserComponent;

Add it all together in your application and voilà!

// src/App.js
import React from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import UserComponent from "./components/UserComponent";
import UserButton from "./components/UserButton";

const queryClient = new QueryClient();

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <div>
        <h1>React Query and Zustand Example</h1>
        <UserButton id={1}>Load User 1</UserButton>
        <UserButton id={2}>Load User 2</UserButton>
        <UserComponent />
      </div>
    </QueryClientProvider>
  );
};

export default App;