Skip to main content

Chapter 15: Full-Stack Project - Building a Complete React Application

Welcome to the final chapter of our React journey! In this comprehensive guide, we'll build a complete full-stack React application that demonstrates all the concepts, patterns, and best practices we've learned throughout this tutorial.

Learning Objectives

By the end of this chapter, you will understand:

  • What a full-stack React application is and how to architect it properly
  • How to plan and structure a complete React project from scratch
  • Why different architectural patterns exist and when to use each approach
  • What the complete development workflow looks like from design to deployment
  • How to implement all React concepts in a real-world application
  • What production considerations are and how to handle them
  • Why this project serves as a portfolio piece and learning milestone

What is a Full-Stack React Project? The Complete Application

What is a Full-Stack React Project?

A full-stack React project is a complete application that includes both frontend (React) and backend components, demonstrating real-world development practices, architecture patterns, and production-ready features.

A full-stack React project is what demonstrates your mastery of React development by combining all the concepts, patterns, and best practices into a cohesive, production-ready application.

What Makes This Project Special?

This project demonstrates:

  1. Complete Architecture: Frontend, backend, database, and deployment
  2. Modern Patterns: React 18+ features, hooks, and advanced patterns
  3. Real-world Features: Authentication, data management, real-time updates
  4. Production Ready: Error handling, testing, performance optimization
  5. Best Practices: Code organization, documentation, and maintainability
  6. Portfolio Quality: Professional-grade application suitable for showcasing

What Will We Build?

We'll create a Task Management Application with the following features:

  • User Authentication: Login, registration, and session management
  • Task Management: Create, read, update, and delete tasks
  • Real-time Collaboration: Live updates when tasks are modified
  • Advanced UI: Modern, responsive design with animations
  • Performance Optimization: Code splitting, lazy loading, and caching
  • Testing: Comprehensive test coverage
  • Deployment: Production-ready deployment configuration

How to Plan the Project? Architecture and Structure

What is Project Architecture?

Project architecture is the high-level structure and organization of your application, including how different parts interact, data flow, and technology choices.

Project architecture is what determines how maintainable, scalable, and performant your application will be, making it crucial to plan carefully from the beginning.

How to Structure the Project?

task-manager/
├── frontend/ # React frontend application
│ ├── public/
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── pages/ # Page components
│ │ ├── hooks/ # Custom hooks
│ │ ├── services/ # API services
│ │ ├── contexts/ # React contexts
│ │ ├── utils/ # Utility functions
│ │ ├── styles/ # CSS and styling
│ │ └── types/ # TypeScript types
│ ├── package.json
│ └── vite.config.js
├── backend/ # Node.js backend API
│ ├── src/
│ │ ├── controllers/ # Route controllers
│ │ ├── models/ # Database models
│ │ ├── routes/ # API routes
│ │ ├── middleware/ # Express middleware
│ │ ├── services/ # Business logic
│ │ └── utils/ # Utility functions
│ ├── package.json
│ └── server.js
├── shared/ # Shared types and utilities
│ ├── types/
│ └── utils/
├── docs/ # Documentation
├── docker-compose.yml # Development environment
└── README.md

How to Choose Technologies?

Frontend Stack:

  • React 18+: Latest React with concurrent features
  • TypeScript: Type safety and better developer experience
  • Vite: Fast build tool and development server
  • React Router: Client-side routing
  • React Query: Data fetching and caching
  • Zustand: State management
  • Tailwind CSS: Utility-first CSS framework
  • React Hook Form: Form handling
  • React Testing Library: Testing utilities

Backend Stack:

  • Node.js: JavaScript runtime
  • Express: Web framework
  • MongoDB: NoSQL database
  • Mongoose: MongoDB object modeling
  • JWT: Authentication tokens
  • Socket.io: Real-time communication
  • Jest: Testing framework
  • ESLint & Prettier: Code quality tools

How to Implement the Frontend? React Application Development

How to Set Up the Frontend?

// frontend/package.json
{
"name": "task-manager-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"@tanstack/react-query": "^4.24.0",
"zustand": "^4.3.0",
"react-hook-form": "^7.43.0",
"socket.io-client": "^4.6.0",
"date-fns": "^2.29.0",
"clsx": "^1.2.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.0",
"eslint": "^8.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.0",
"postcss": "^8.4.0",
"tailwindcss": "^3.2.0",
"typescript": "^4.9.0",
"vite": "^4.1.0",
"vitest": "^0.28.0"
}
}

How to Implement the Main Application?

// frontend/src/App.tsx
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { AuthProvider } from './contexts/AuthContext';
import { SocketProvider } from './contexts/SocketContext';
import { AppRoutes } from './routes/AppRoutes';
import { ErrorBoundary } from './components/ErrorBoundary';
import { Toaster } from './components/ui/Toaster';
import './styles/globals.css';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
refetchOnWindowFocus: false,
},
},
});

function App() {
return (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<SocketProvider>
<div className="min-h-screen bg-gray-50">
<AppRoutes />
<Toaster />
</div>
</SocketProvider>
</AuthProvider>
</BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</ErrorBoundary>
);
}

export default App;

How to Implement Authentication?

// frontend/src/contexts/AuthContext.tsx
import { createContext, useContext, useReducer, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { authService } from '../services/authService';
import { userService } from '../services/userService';

interface User {
id: string;
name: string;
email: string;
avatar?: string;
role: 'user' | 'admin';
}

interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}

type AuthAction =
| { type: 'AUTH_START' }
| { type: 'AUTH_SUCCESS'; payload: User }
| { type: 'AUTH_FAILURE'; payload: string }
| { type: 'LOGOUT' }
| { type: 'UPDATE_USER'; payload: Partial<User> };

const initialState: AuthState = {
user: null,
isAuthenticated: false,
isLoading: true,
error: null,
};

function authReducer(state: AuthState, action: AuthAction): AuthState {
switch (action.type) {
case 'AUTH_START':
return { ...state, isLoading: true, error: null };
case 'AUTH_SUCCESS':
return {
...state,
isLoading: false,
isAuthenticated: true,
user: action.payload,
error: null,
};
case 'AUTH_FAILURE':
return {
...state,
isLoading: false,
isAuthenticated: false,
user: null,
error: action.payload,
};
case 'LOGOUT':
return {
...state,
isAuthenticated: false,
user: null,
error: null,
};
case 'UPDATE_USER':
return {
...state,
user: state.user ? { ...state.user, ...action.payload } : null,
};
default:
return state;
}
}

interface AuthContextType extends AuthState {
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
register: (name: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>;
logout: () => void;
updateUser: (userData: Partial<User>) => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(authReducer, initialState);
const navigate = useNavigate();

useEffect(() => {
const initAuth = async () => {
const token = localStorage.getItem('authToken');
if (token) {
try {
const user = await userService.getCurrentUser();
dispatch({ type: 'AUTH_SUCCESS', payload: user });
} catch (error) {
localStorage.removeItem('authToken');
dispatch({ type: 'AUTH_FAILURE', payload: 'Session expired' });
}
} else {
dispatch({ type: 'AUTH_FAILURE', payload: 'No token' });
}
};

initAuth();
}, []);

const login = async (email: string, password: string) => {
try {
dispatch({ type: 'AUTH_START' });
const { user, token } = await authService.login(email, password);

localStorage.setItem('authToken', token);
dispatch({ type: 'AUTH_SUCCESS', payload: user });

return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Login failed';
dispatch({ type: 'AUTH_FAILURE', payload: errorMessage });
return { success: false, error: errorMessage };
}
};

const register = async (name: string, email: string, password: string) => {
try {
dispatch({ type: 'AUTH_START' });
const { user, token } = await authService.register(name, email, password);

localStorage.setItem('authToken', token);
dispatch({ type: 'AUTH_SUCCESS', payload: user });

return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Registration failed';
dispatch({ type: 'AUTH_FAILURE', payload: errorMessage });
return { success: false, error: errorMessage };
}
};

const logout = () => {
localStorage.removeItem('authToken');
dispatch({ type: 'LOGOUT' });
navigate('/login');
};

const updateUser = async (userData: Partial<User>) => {
try {
const updatedUser = await userService.updateUser(userData);
dispatch({ type: 'UPDATE_USER', payload: updatedUser });
} catch (error) {
console.error('Failed to update user:', error);
}
};

const value: AuthContextType = {
...state,
login,
register,
logout,
updateUser,
};

return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}

export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}

How to Implement Task Management?

// frontend/src/components/TaskList.tsx
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { taskService } from '../services/taskService';
import { TaskCard } from './TaskCard';
import { TaskForm } from './TaskForm';
import { TaskFilters } from './TaskFilters';
import { Button } from './ui/Button';
import { PlusIcon } from './ui/icons/PlusIcon';
import { useSocket } from '../hooks/useSocket';

interface Task {
id: string;
title: string;
description: string;
status: 'todo' | 'in-progress' | 'done';
priority: 'low' | 'medium' | 'high';
assignee?: {
id: string;
name: string;
avatar?: string;
};
createdAt: string;
updatedAt: string;
}

export function TaskList() {
const [showForm, setShowForm] = useState(false);
const [filters, setFilters] = useState({
status: 'all',
priority: 'all',
assignee: 'all',
});

const queryClient = useQueryClient();
const { socket } = useSocket();

// Fetch tasks
const { data: tasks = [], isLoading, error } = useQuery({
queryKey: ['tasks', filters],
queryFn: () => taskService.getTasks(filters),
});

// Create task mutation
const createTaskMutation = useMutation({
mutationFn: taskService.createTask,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
setShowForm(false);
},
});

// Update task mutation
const updateTaskMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Task> }) =>
taskService.updateTask(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});

// Delete task mutation
const deleteTaskMutation = useMutation({
mutationFn: taskService.deleteTask,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});

// Listen for real-time updates
useSocket('task:created', () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
});

useSocket('task:updated', () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
});

useSocket('task:deleted', () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
});

const handleCreateTask = (taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>) => {
createTaskMutation.mutate(taskData);
};

const handleUpdateTask = (id: string, data: Partial<Task>) => {
updateTaskMutation.mutate({ id, data });
};

const handleDeleteTask = (id: string) => {
if (window.confirm('Are you sure you want to delete this task?')) {
deleteTaskMutation.mutate(id);
}
};

if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
);
}

if (error) {
return (
<div className="text-center py-8">
<p className="text-red-600">Failed to load tasks</p>
<Button onClick={() => queryClient.invalidateQueries({ queryKey: ['tasks'] })}>
Retry
</Button>
</div>
);
}

return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Tasks</h1>
<Button
onClick={() => setShowForm(true)}
className="flex items-center gap-2"
>
<PlusIcon className="h-4 w-4" />
Add Task
</Button>
</div>

<TaskFilters filters={filters} onFiltersChange={setFilters} />

{showForm && (
<TaskForm
onSubmit={handleCreateTask}
onCancel={() => setShowForm(false)}
isLoading={createTaskMutation.isPending}
/>
)}

<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
onUpdate={handleUpdateTask}
onDelete={handleDeleteTask}
isLoading={updateTaskMutation.isPending}
/>
))}
</div>

{tasks.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">No tasks found</p>
<Button
onClick={() => setShowForm(true)}
className="mt-4"
>
Create your first task
</Button>
</div>
)}
</div>
);
}

How to Implement the Backend? Node.js API Development

How to Set Up the Backend?

// backend/package.json
{
"name": "task-manager-backend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "nodemon src/server.js",
"start": "node src/server.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src --ext js",
"lint:fix": "eslint src --ext js --fix"
},
"dependencies": {
"express": "^4.18.0",
"mongoose": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.0",
"cors": "^2.8.0",
"helmet": "^6.0.0",
"express-rate-limit": "^6.7.0",
"socket.io": "^4.6.0",
"dotenv": "^16.0.0",
"joi": "^17.7.0",
"multer": "^1.4.0"
},
"devDependencies": {
"nodemon": "^2.0.0",
"jest": "^29.0.0",
"supertest": "^6.3.0",
"eslint": "^8.0.0"
}
}

How to Implement the Server?

// backend/src/server.js
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { createServer } from 'http';
import { Server } from 'socket.io';
import dotenv from 'dotenv';
import mongoose from 'mongoose';

import authRoutes from './routes/auth.js';
import userRoutes from './routes/users.js';
import taskRoutes from './routes/tasks.js';
import { errorHandler } from './middleware/errorHandler.js';
import { notFound } from './middleware/notFound.js';
import { socketHandler } from './socket/socketHandler.js';

dotenv.config();

const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
methods: ['GET', 'POST'],
},
});

// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});

// Middleware
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true,
}));
app.use(limiter);
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
app.use('/api/tasks', taskRoutes);

// Health check
app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

// Error handling
app.use(notFound);
app.use(errorHandler);

// Socket.io
socketHandler(io);

// Database connection
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/taskmanager', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log('Connected to MongoDB');
})
.catch((error) => {
console.error('MongoDB connection error:', error);
process.exit(1);
});

const PORT = process.env.PORT || 5000;

server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

How to Implement Task Models and Controllers?

// backend/src/models/Task.js
import mongoose from 'mongoose';

const taskSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
maxlength: 100,
},
description: {
type: String,
trim: true,
maxlength: 500,
},
status: {
type: String,
enum: ['todo', 'in-progress', 'done'],
default: 'todo',
},
priority: {
type: String,
enum: ['low', 'medium', 'high'],
default: 'medium',
},
assignee: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
dueDate: {
type: Date,
},
tags: [{
type: String,
trim: true,
}],
}, {
timestamps: true,
});

// Indexes
taskSchema.index({ createdBy: 1, status: 1 });
taskSchema.index({ assignee: 1, status: 1 });
taskSchema.index({ createdAt: -1 });

export const Task = mongoose.model('Task', taskSchema);
// backend/src/controllers/taskController.js
import { Task } from '../models/Task.js';
import { User } from '../models/User.js';
import { asyncHandler } from '../utils/asyncHandler.js';
import { AppError } from '../utils/AppError.js';

// Get all tasks with filtering and pagination
export const getTasks = asyncHandler(async (req, res) => {
const { status, priority, assignee, page = 1, limit = 10 } = req.query;
const userId = req.user.id;

// Build filter object
const filter = { createdBy: userId };

if (status && status !== 'all') {
filter.status = status;
}

if (priority && priority !== 'all') {
filter.priority = priority;
}

if (assignee && assignee !== 'all') {
filter.assignee = assignee;
}

// Calculate pagination
const skip = (page - 1) * limit;

// Execute query
const tasks = await Task.find(filter)
.populate('assignee', 'name avatar')
.populate('createdBy', 'name')
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit));

const total = await Task.countDocuments(filter);

res.json({
tasks,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit),
},
});
});

// Get single task
export const getTask = asyncHandler(async (req, res) => {
const { id } = req.params;
const userId = req.user.id;

const task = await Task.findOne({ _id: id, createdBy: userId })
.populate('assignee', 'name avatar')
.populate('createdBy', 'name');

if (!task) {
throw new AppError('Task not found', 404);
}

res.json(task);
});

// Create new task
export const createTask = asyncHandler(async (req, res) => {
const taskData = {
...req.body,
createdBy: req.user.id,
};

const task = await Task.create(taskData);

// Populate the created task
await task.populate('assignee', 'name avatar');
await task.populate('createdBy', 'name');

// Emit real-time event
req.io.emit('task:created', task);

res.status(201).json(task);
});

// Update task
export const updateTask = asyncHandler(async (req, res) => {
const { id } = req.params;
const userId = req.user.id;

const task = await Task.findOneAndUpdate(
{ _id: id, createdBy: userId },
req.body,
{ new: true, runValidators: true }
)
.populate('assignee', 'name avatar')
.populate('createdBy', 'name');

if (!task) {
throw new AppError('Task not found', 404);
}

// Emit real-time event
req.io.emit('task:updated', task);

res.json(task);
});

// Delete task
export const deleteTask = asyncHandler(async (req, res) => {
const { id } = req.params;
const userId = req.user.id;

const task = await Task.findOneAndDelete({ _id: id, createdBy: userId });

if (!task) {
throw new AppError('Task not found', 404);
}

// Emit real-time event
req.io.emit('task:deleted', { id: task._id });

res.json({ message: 'Task deleted successfully' });
});

// Get task statistics
export const getTaskStats = asyncHandler(async (req, res) => {
const userId = req.user.id;

const stats = await Task.aggregate([
{ $match: { createdBy: mongoose.Types.ObjectId(userId) } },
{
$group: {
_id: '$status',
count: { $sum: 1 },
},
},
]);

const priorityStats = await Task.aggregate([
{ $match: { createdBy: mongoose.Types.ObjectId(userId) } },
{
$group: {
_id: '$priority',
count: { $sum: 1 },
},
},
]);

res.json({
statusStats: stats,
priorityStats: priorityStats,
});
});

How to Implement Real-time Features? WebSocket Integration

How to Set Up Socket.io?

// backend/src/socket/socketHandler.js
import jwt from 'jsonwebtoken';
import { User } from '../models/User.js';

export const socketHandler = (io) => {
// Authentication middleware for socket connections
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication error'));
}

const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id).select('-password');

if (!user) {
return next(new Error('User not found'));
}

socket.userId = user._id.toString();
socket.user = user;
next();
} catch (error) {
next(new Error('Authentication error'));
}
});

io.on('connection', (socket) => {
console.log(`User ${socket.user.name} connected`);

// Join user to their personal room
socket.join(`user:${socket.userId}`);

// Join project rooms if needed
socket.on('join:project', (projectId) => {
socket.join(`project:${projectId}`);
});

socket.on('leave:project', (projectId) => {
socket.leave(`project:${projectId}`);
});

// Handle task updates
socket.on('task:update', (data) => {
// Broadcast to all users in the project
socket.to(`project:${data.projectId}`).emit('task:updated', data);
});

// Handle typing indicators
socket.on('typing:start', (data) => {
socket.to(`project:${data.projectId}`).emit('user:typing', {
userId: socket.userId,
userName: socket.user.name,
});
});

socket.on('typing:stop', (data) => {
socket.to(`project:${data.projectId}`).emit('user:stopped-typing', {
userId: socket.userId,
});
});

socket.on('disconnect', () => {
console.log(`User ${socket.user.name} disconnected`);
});
});
};

How to Implement Frontend Socket Integration?

// frontend/src/contexts/SocketContext.tsx
import { createContext, useContext, useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { useAuth } from './AuthContext';

interface SocketContextType {
socket: Socket | null;
isConnected: boolean;
}

const SocketContext = createContext<SocketContextType | undefined>(undefined);

export function SocketProvider({ children }: { children: React.ReactNode }) {
const [socket, setSocket] = useState<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const { user, isAuthenticated } = useAuth();

useEffect(() => {
if (isAuthenticated && user) {
const token = localStorage.getItem('authToken');

const newSocket = io(process.env.REACT_APP_API_URL || 'http://localhost:5000', {
auth: {
token,
},
});

newSocket.on('connect', () => {
console.log('Connected to server');
setIsConnected(true);
});

newSocket.on('disconnect', () => {
console.log('Disconnected from server');
setIsConnected(false);
});

newSocket.on('connect_error', (error) => {
console.error('Connection error:', error);
setIsConnected(false);
});

setSocket(newSocket);

return () => {
newSocket.close();
};
}
}, [isAuthenticated, user]);

const value: SocketContextType = {
socket,
isConnected,
};

return (
<SocketContext.Provider value={value}>
{children}
</SocketContext.Provider>
);
}

export function useSocket() {
const context = useContext(SocketContext);
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
}

// Custom hook for socket events
export function useSocketEvent(event: string, callback: (...args: any[]) => void) {
const { socket } = useSocket();

useEffect(() => {
if (socket) {
socket.on(event, callback);

return () => {
socket.off(event, callback);
};
}
}, [socket, event, callback]);
}

How to Deploy the Application? Production Deployment

How to Set Up Docker?

# frontend/Dockerfile
FROM node:18-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# backend/Dockerfile
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
EXPOSE 5000
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'

services:
frontend:
build: ./frontend
ports:
- "3000:80"
depends_on:
- backend
environment:
- REACT_APP_API_URL=http://localhost:5000

backend:
build: ./backend
ports:
- "5000:5000"
depends_on:
- mongodb
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongodb:27017/taskmanager
- JWT_SECRET=your-secret-key
- FRONTEND_URL=http://localhost:3000

mongodb:
image: mongo:5.0
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db

volumes:
mongodb_data:

How to Set Up CI/CD?

# .github/workflows/deploy.yml
name: Deploy Task Manager

on:
push:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: |
cd frontend && npm ci
cd ../backend && npm ci

- name: Run tests
run: |
cd frontend && npm test
cd ../backend && npm test

- name: Build frontend
run: |
cd frontend && npm run build

deploy:
needs: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Deploy to production
run: |
# Add your deployment commands here
echo "Deploying to production..."

Why is This Project Important? The Learning Journey

Why Build a Full-Stack Project?

Building a full-stack project provides several critical benefits:

  1. Complete Understanding: Demonstrates mastery of all React concepts
  2. Real-world Experience: Shows how to apply knowledge in practice
  3. Portfolio Piece: Creates a professional-quality project to showcase
  4. Problem Solving: Develops skills in debugging and optimization
  5. Best Practices: Reinforces proper development practices
  6. Career Preparation: Prepares for real-world development challenges

Why This Project Demonstrates Mastery?

This project showcases:

  1. React Fundamentals: Components, hooks, state management
  2. Advanced Patterns: Custom hooks, context, performance optimization
  3. Backend Integration: API communication, authentication, real-time features
  4. Modern Tools: TypeScript, testing, build tools, deployment
  5. Best Practices: Code organization, error handling, security
  6. Production Readiness: Performance, scalability, maintainability

Why This Project Serves as a Portfolio Piece?

This project demonstrates:

  1. Technical Skills: Full-stack development capabilities
  2. Problem Solving: Ability to build complex applications
  3. Best Practices: Professional development standards
  4. Modern Technologies: Up-to-date with current trends
  5. User Experience: Focus on creating great user experiences
  6. Production Quality: Ready for real-world deployment

Summary: Completing Your React Journey

What Have We Accomplished?

Throughout this comprehensive React tutorial, we've covered:

  1. React Fundamentals: Core concepts and building blocks
  2. Component Architecture: Design patterns and best practices
  3. State Management: Local and global state solutions
  4. Event Handling: User interactions and form management
  5. Advanced Hooks: Performance optimization and custom logic
  6. Context API: Global state management and data sharing
  7. Performance Optimization: Speed and efficiency techniques
  8. Routing & Navigation: Single-page application navigation
  9. Advanced Patterns: Sophisticated development techniques
  10. Testing: Comprehensive testing strategies
  11. Build Tools: Modern development workflow
  12. Production Deployment: Real-world deployment practices
  13. Backend Integration: Full-stack application development
  14. Modern React Patterns: React 18+ features and techniques
  15. Full-Stack Project: Complete application development

How to Continue Your React Journey?

  1. Practice: Build more projects to reinforce learning
  2. Explore: Dive deeper into specific areas of interest
  3. Contribute: Contribute to open-source React projects
  4. Stay Updated: Follow React updates and new features
  5. Network: Connect with other React developers
  6. Teach: Share your knowledge with others

Why This Knowledge Matters?

Understanding React deeply enables you to:

  • Build Better Applications: Create more efficient and user-friendly apps
  • Solve Complex Problems: Handle real-world development challenges
  • Advance Your Career: Stand out in the competitive job market
  • Contribute to the Community: Help others learn and grow
  • Stay Relevant: Keep up with evolving web development trends
  • Create Impact: Build applications that make a difference

Final Thoughts

Congratulations on completing this comprehensive React tutorial! You now have the knowledge and skills to build professional-quality React applications. Remember that learning is a continuous journey, and the best way to improve is through practice and building real projects.

Your React journey doesn't end here—it's just beginning. Use this knowledge as a foundation to explore new technologies, tackle challenging problems, and build amazing applications that users will love.

Happy coding! 🚀