A small light-weight state management library.
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>
);
};
A way to effectively deal with data management, error handling and caching. Sometimes used as a state management library.
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"));
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.
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;