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:

  1. Initialize a React project with TypeScript.
  2. Install and configure TanStack/React Query.
  3. Create API calls to interact with the backend (using Axios).
  4. 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 frontend

2.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-react

3. Configuring React Query

3.1. Create a React Query Client

  1. File: src/queryClient.ts
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient();

export default queryClient;

  1. 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.

  1. 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

  1. Start the backend server (if it’s not running):
cd backend
npm run dev
  1. Start the React frontend:
cd frontend
npm start

Open 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

  1. Add a checkbox to toggle a todo's completed status (hint: create a new API route and React Query mutation).
  2. Style the components using Radix UI for better UI/UX.
  3. Add loading indicators for the Add and Delete buttons using React Query's isLoading state.

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.

Copyright © Big Poppa Code & Progress and Fortune LLC 2025