redux toolkit query and react query
npx create-next-app@latest my-redux-crud-app --typescript
cd my-redux-crud-app
npm install @reduxjs/toolkit @reduxjs/toolkit/query axios
// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import { usersApi } from './usersSlice'; // Import users slice (created later)
export const store = configureStore({
reducer: {
[usersApi.reducerPath]: usersApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(usersApi.middleware),
});
// Infer the store type for TypeScript
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// src/store/usersSlice.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
// Replace with your API endpoint URL if applicable
const baseUrl = 'https://your-api.com';
// Define the user interface type
interface User {
id: number;
name: string;
email: string;
}
export const usersApi = createApi({
reducerPath: 'users',
baseQuery: fetchBaseQuery({ baseUrl }),
endpoints: (builder) => ({
getUsers: builder.query<User[], void>({
query: () => '/users',
}),
getUserById: builder.query<User, number>({
query: (id) => `/users/${id}`,
}),
createUser: builder.mutation<User, User>({
query: (newUser) => ({
url: '/users',
method: 'POST',
body: newUser,
}),
}),
updateUser: builder.mutation<User, User>({
query: (updatedUser) => ({
url: `/users/${updatedUser.id}`,
method: 'PUT',
body: updatedUser,
}),
}),
deleteUser: builder.mutation<void, number>({
query: (id) => ({
url: `/users/${id}`,
method: 'DELETE',
}),
}),
}),
});
export const {
useGetUsersQuery,
useGetUserByIdQuery,
useCreateUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
} = usersApi;
// src/app/layout.tsx
import type { Metadata } from 'next';
import { Provider } from 'react-redux';
import { store } from './store/index';
interface RootLayoutProps {
children: React.ReactNode;
}
export default async function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en" >
<Provider store={store}>
<body>
{children}
</body>
</Provider>
</html>
);
}
src/components/
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useGetUsersQuery } from '../store/usersSlice';
interface User {
id: number;
name: string;
email: string;
}
const UserList = () => {
const dispatch = useDispatch();
const { data: users, isLoading, error } = useGetUsersQuery();
if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
// Handle specific errors based on error.code or error.message
return <p>Error: {error.message}</p>;
}
return (
<div>
<h1>Users</h1>
<ul>
{users.map((user: User) => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
};
export default UserList;
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useGetUserByIdQuery, useUpdateUserMutation } from '../store/usersSlice';
interface User {
id: number;
name: string;
email: string;
}
const UserDetails = ({ userId }: { userId: number }) => {
const dispatch = useDispatch();
const { data: user, isLoading, error } = useGetUserByIdQuery(userId);
const [updatedUser, setUpdatedUser] = useState<User | null>(null);
// Handle loading and error states as needed
const handleUpdateUser = async () => {
if (!updatedUser) return; // Handle potential errors or validation
try {
await dispatch(useUpdateUserMutation(updatedUser).unwrap());
// User updated successfully, handle success state
} catch (error) {
console.error('Error updating user:', error);
// Handle update error
}
};
return (
<div>
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : user ? (
<div>
<h1>User Details</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<form onSubmit={handleUpdateUser}>
{/* Form fields to edit user details */}
<button type="submit">Update User</button>
</form>
</div>
) : (
<p>User not found</p>
)}
</div>
);
};
export default UserDetails;
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useCreateUserMutation } from '../store/usersSlice';
interface User {
id: number;
name: string;
email: string;
}
const UserForm = () => {
const dispatch = useDispatch();
const [newUser, setNewUser] = useState<User>({ name: '', email: '' });
const handleCreateUser = async () => {
try {
const result = await dispatch(useCreateUserMutation(newUser).unwrap());
// User created successfully, handle success state (e.g., redirect)
console.log('User created:', result);
} catch (error) {
console.error('Error creating user:', error);
// Handle creation error (e.g., display error message)
}
};
return (
<div>
<h1>Create User</h1>
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
value={newUser.name}
onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
/>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
/>
<button type="submit" onClick={handleCreateUser}>
Create User
</button>
</form>
</div>
);
};
export default UserForm;
import React from 'react';
import { useDispatch } from 'react-redux';
import { useDeleteUserMutation } from '../store/usersSlice';
const UserDelete = ({ userId }: { userId: number }) => {
const dispatch = useDispatch();
const [isDeleting, setIsDeleting] = useState(false);
const [deleteError, setDeleteError] = useState<string | null>(null);
const handleDeleteUser = async () => {
setIsDeleting(true);
setDeleteError(null);
try {
await dispatch(useDeleteUserMutation(userId).unwrap());
// User deleted successfully, handle success state (e.g., redirect)
console.log('User deleted:', userId);
} catch (error) {
console.error('Error deleting user:', error);
setDeleteError(error.message); // Handle deletion error
} finally {
setIsDeleting(false);
}
};
return (
<div>
{isDeleting ? (
<p>Deleting...</p>
) : deleteError ? (
<p>Error: {deleteError}</p>
) : (
<button onClick={handleDeleteUser}>Delete User</button>
)}
</div>
);
};
export default UserDelete;
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useGetUserByIdQuery, useUpdateUserMutation } from '../store/usersSlice';
interface User {
id: number;
name: string;
email: string;
}
const UserDetails = ({ userId }: { userId: number }) => {
const dispatch = useDispatch();
const { data: user, isLoading, error } = useGetUserByIdQuery(userId);
const [updatedUser, setUpdatedUser] = useState<User | null>(null);
// Handle loading and error states as needed
const handleUpdateUser = async () => {
if (!updatedUser) return; // Handle potential errors or validation
try {
await dispatch(useUpdateUserMutation(updatedUser).unwrap());
// User updated successfully, handle success state (e.g., show a message)
console.log('User updated:', updatedUser);
setUpdatedUser(null); // Reset updated user state
} catch (error) {
console.error('Error updating user:', error);
// Handle update error (e.g., display an error message)
}
};
return (
<div>
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : user ? (
<div>
<h1>User Details</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<form onSubmit={handleUpdateUser}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
value={updatedUser?.name ?? user.name} // Use updated value if available
onChange={(e) =>
setUpdatedUser({ ...updatedUser, name: e.target.value })
}
/>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
value={updatedUser?.email ?? user.name} // Use updated value if available
onChange={(e) =>
setUpdatedUser({ ...updatedUser, email: e.target.value })
}
/>
<button type="submit">Update User</button>
</form>
</div>
) : (
<p>User not found</p>
)}
</div>
);
};
export default UserDetails;
React Query
npm install react-query
// src/queryClient.ts
import { createClient, type QueryClient } from '@tanstack/react-query';
export const queryClient = createClient(); // Create the React Query client
import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './queryClient'; // Import the client
import MyApp from './MyApp';
function App({ Component, pageProps }) {
return (
<QueryClientProvider client={queryClient}>
<MyApp {...pageProps} />
</QueryClientProvider>
);
}
export default App;
// usersApi.ts
import { useQuery, useMutation, type QueryKey } from '@tanstack/react-query';
import axios from 'axios'; // Replace with your request library (Axios/Fetch)
// Replace with your API endpoint URL if applicable
const baseUrl = 'https://your-api.com/users';
// Interface for User data
interface User {
id: number;
name: string;
email: string;
}
// Use Query functions
export const getUsers = async (): Promise<User[]> => {
const response = await axios.get(baseUrl);
return response.data;
};
export const useGetUsersQuery = () => {
return useQuery<User[], Error>('users', getUsers); // Use a unique query key
};
export const getUserById = async (id: number): Promise<User> => {
const response = await axios.get(`<span class="math-inline">\{baseUrl\}/</span>{id}`);
return response.data;
};
export const useGetUserByIdQuery = (id: number) => {
return useQuery<User, Error>(['user', id], () => getUserById(id)); // Use query key with ID
};
// Use Mutation functions
export const createUser = async (newUser: User): Promise<User> => {
const response = await axios.post(baseUrl, newUser);
return response.data;
};
export const useCreateUserMutation = () => {
return useMutation<User, Error, User>(createUser);
};
export const updateUser = async (updatedUser: User): Promise<User> => {
const response = await axios.put(`<span class="math-inline">\{baseUrl\}/</span>{updatedUser.id}`, updatedUser);
return response.data;
};
export const useUpdateUserMutation = () => {
return useMutation<User, Error, User>(updateUser);
};
export const deleteUser = async (id: number): Promise<void> => {
await axios.delete(`<span class="math-inline">\{baseUrl\}/</span>{id}`);
};
export const useDeleteUserMutation = () => {
return useMutation<void, Error, number>(deleteUser);
};
import React from 'react';
import { useGetUsersQuery } from './usersApi';
interface User {
id: number;
name: string;
email: string;
}
const UserList = () => {
const { isLoading, error, data: users } = useGetUsersQuery();
// Handle loading and error states as needed
return (
<div>
<h1>Users</h1>
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : (
<ul>
{users.map((user: User) => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
)}
</div>
);
};
export default UserList;
import React from 'react';
import { useGetUserByIdQuery, useDeleteUserMutation } from './usersApi';
interface User {
id: number;
name: string;
email: string;
}
const UserDetails = ({ userId }: { userId: number }) => {
const { isLoading, error, data: user } = useGetUserByIdQuery(userId);
const { mutate: deleteUser, isLoading: isDeleting, error: deleteError } = useDeleteUserMutation();
const handleDeleteUser = async () => {
if (confirm('Are you sure you want to delete this user?')) {
await deleteUser(userId);
// Handle deletion success (e.g., redirect)
}
};
return (
<div>
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : user ? (
<div>
<h1>User Details</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<button onClick={handleDeleteUser} disabled={isDeleting}>
{isDeleting ? 'Deleting...' : 'Delete User'}
</button>
</div>
) : (
<p>User not found</p>
)}
{deleteError && <p>Error deleting user: {deleteError.message}</p>}
</div>
);
};
export default UserDetails;
import React, { useState } from 'react';
import { useCreateUserMutation } from './usersApi';
interface User {
id: number;
name: string;
email: string;
}
const UserForm = () => {
const [newUser, setNewUser] = useState<User>({ name: '', email: '' });
const { mutate: createUser, isLoading, error } = useCreateUserMutation();
const handleCreateUser = async () => {
try {
await createUser(newUser);
// Handle creation success (e.g., reset form or redirect)
console.log('User created:', newUser);
setNewUser({ name: '', email: '' }); // Reset form after successful creation
} catch (error) {
console.error('Error creating user:', error);
// Handle creation error (e.g., display error message)
}
};
return (
<div>
<h1>Create User</h1>
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
value={newUser.name}
onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
/>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create User'}
</button>
</form>
{error && <p>Error: {error.message}</p>}
</div>
);
};
export default UserForm;