Lesson 7: Setting Up the Frontend with React and TanStack/React Query
Goal
Set up the React frontend for the TodoList app and connect it to the backend. Use React Query for fetching and managing server state, ensuring the app is responsive and optimized.
1. Learning Objectives
By the end of this lesson, students will:
- Initialize a React project with TypeScript.
- Install and configure TanStack/React Query.
- Create API calls to interact with the backend (using Axios).
- Display fetched data and handle loading and error states.
2. Setting Up the React Project
2.1. Initialize the React Project
Run the following command to create a React project with TypeScript:
npx create-react-app frontend --template typescript
cd frontend2.2. Install Dependencies
We need Axios for HTTP requests and React Query for data fetching.
npm install axios @tanstack/react-query @tanstack/react-router @radix-ui/react-checkbox phosphor-react3. Configuring React Query
3.1. Create a React Query Client
- File:
src/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient();
export default queryClient;- Wrap the App with QueryClientProvider
Update src/index.tsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClientProvider } from '@tanstack/react-query';
import queryClient from './queryClient';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);4. Setting Up API Calls
Create a reusable API service using Axios.
- File:
src/api/todoApi.ts
import axios from 'axios';
const API_URL = 'http://localhost:5000/api/todos';
export const fetchTodos = async () => {
const response = await axios.get(API_URL);
return response.data;
};
export const createTodo = async (title: string) => {
const response = await axios.post(API_URL, { title });
return response.data;
};
export const deleteTodo = async (id: string) => {
await axios.delete(`${API_URL}/${id}`);
};5. Displaying Todos with React Query
We will now display todos fetched from the backend and manage state using React Query.
5.1. Todo List Component
File: src/components/TodoList.tsx
import React from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { fetchTodos, deleteTodo } from '../api/todoApi';
import TodoItem from './TodoItem';
const TodoList: React.FC = () => {
const queryClient = useQueryClient();
// Fetch Todos
const { data: todos, isLoading, error } = useQuery(['todos'], fetchTodos);
// Delete Mutation
const deleteMutation = useMutation(deleteTodo, {
onSuccess: () => {
queryClient.invalidateQueries(['todos']); // Refresh the todos list
},
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error fetching todos</p>;
return (
<div>
<h2>My Todos</h2>
{todos.map((todo: any) => (
<TodoItem
key={todo._id}
id={todo._id}
title={todo.title}
completed={todo.completed}
onDelete={() => deleteMutation.mutate(todo._id)}
/>
))}
</div>
);
};
export default TodoList;5.2. Todo Item Component
File: src/components/TodoItem.tsx
import React from 'react';
interface TodoItemProps {
id: string;
title: string;
completed: boolean;
onDelete: () => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ title, completed, onDelete }) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{title} {completed ? '(Completed)' : ''}</span>
<button onClick={onDelete}>Delete</button>
</div>
);
};
export default TodoItem;5.3. Add Todos with Mutation
Add a form to create a new todo using React Query Mutations.
File: src/components/AddTodo.tsx
import React, { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTodo } from '../api/todoApi';
const AddTodo: React.FC = () => {
const [title, setTitle] = useState('');
const queryClient = useQueryClient();
const mutation = useMutation(createTodo, {
onSuccess: () => {
queryClient.invalidateQueries(['todos']);
setTitle('');
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (title) mutation.mutate(title);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Add a new task"
/>
<button type="submit">Add</button>
</form>
);
};
export default AddTodo;6. Connecting Components in App
Update src/App.tsx to include the TodoList and AddTodo components.
import React from 'react';
import AddTodo from './components/AddTodo';
import TodoList from './components/TodoList';
const App: React.FC = () => {
return (
<div>
<h1>Todo List</h1>
<AddTodo />
<TodoList />
</div>
);
};
export default App;7. Running the Frontend
- Start the backend server (if it’s not running):
cd backend
npm run dev- Start the React frontend:
cd frontend
npm startOpen http://localhost:3000 in your browser. You should see:
- A form to add tasks.
- A list of todos fetched from the backend.
- The ability to delete tasks.
8. Homework Assignment
- Add a checkbox to toggle a todo's
completedstatus (hint: create a new API route and React Query mutation). - Style the components using Radix UI for better UI/UX.
- Add loading indicators for the Add and Delete buttons using React Query's
isLoadingstate.
Next Lesson Preview: Lesson 8
In Lesson 8, we will:
- Explore TanStack/React Router to create a multi-page app.
- Add routes for the Todo list and AI-powered features.
