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;