We use cookies to enhance your experience on the site
CodeWorlds

Project: Advanced React 18+ Application

In this project, you will create a modern React application using all the React 18+ features you have learned. You will build an Advanced Task Management System - an advanced task management system utilizing Concurrent Features, Server Components, and modern hooks.

Project Specification

Main Features

  1. Dashboard with real-time updates

    • Using useTransition and useDeferredValue
    • Optimistic updates with useOptimistic
    • Content streaming with Suspense
  2. Advanced forms

    • useFormStatus and useActionState
    • Server Actions
    • Real-time validation
  3. Notification system

    • useId for accessibility
    • Concurrent rendering
    • Progressive enhancement
  4. Performance and optimization

    • React Compiler (optional)
    • Automatic Batching
    • Selective Hydration

Application Structure

1src/
2β”œβ”€β”€ app/
3β”‚   β”œβ”€β”€ layout.tsx          # Root layout (Server Component)
4β”‚   β”œβ”€β”€ page.tsx            # Home page
5β”‚   β”œβ”€β”€ dashboard/
6β”‚   β”‚   β”œβ”€β”€ page.tsx        # Dashboard (Server Component)
7β”‚   β”‚   └── loading.tsx     # Loading UI
8β”‚   β”œβ”€β”€ tasks/
9β”‚   β”‚   β”œβ”€β”€ page.tsx        # Tasks list
10β”‚   β”‚   β”œβ”€β”€ [id]/page.tsx   # Task details
11β”‚   β”‚   └── new/page.tsx    # Create task
12β”‚   └── api/
13β”‚       β”œβ”€β”€ tasks/route.ts  # Tasks API
14β”‚       └── notifications/route.ts
15β”œβ”€β”€ components/
16β”‚   β”œβ”€β”€ ui/                 # Basic UI components
17β”‚   β”œβ”€β”€ forms/              # Form components
18β”‚   β”œβ”€β”€ dashboard/          # Dashboard components
19β”‚   └── tasks/              # Task components
20β”œβ”€β”€ hooks/                  # Custom hooks
21β”œβ”€β”€ lib/                    # Utilities
22└── types/                  # TypeScript types

Step-by-step Implementation

Step 1: Basic structure and types

1// types/index.ts
2export interface Task {
3  id: string;
4  title: string;
5  description: string;
6  status: 'pending' | 'in_progress' | 'completed';
7  priority: 'low' | 'medium' | 'high';
8  assignee?: string;
9  dueDate?: Date;
10  createdAt: Date;
11  updatedAt: Date;
12}
13
14export interface User {
15  id: string;
16  name: string;
17  email: string;
18  avatar?: string;
19}
20
21export interface Notification {
22  id: string;
23  type: 'task_created' | 'task_updated' | 'task_completed';
24  message: string;
25  timestamp: Date;
26  read: boolean;
27}

Step 2: Root Layout with Server Components

1// app/layout.tsx (Server Component)
2import { Suspense } from 'react';
3import { Navigation } from '@/components/Navigation';
4import { NotificationProvider } from '@/components/NotificationProvider';
5
6async function RootLayout({ children }: { children: React.ReactNode }) {
7  // Fetch user data on the server
8  const user = await getCurrentUser();
9  const notifications = await getUnreadNotifications(user.id);
10
11  return (
12    <html lang="en">
13      <head>
14        <title>Advanced Task Manager</title>
15        <meta name="description" content="Advanced task management system" />
16      </head>
17      <body>
18        <NotificationProvider initialNotifications={notifications}>
19          <div className="app-layout">
20            <Suspense fallback={<NavigationSkeleton />}>
21              <Navigation user={user} />
22            </Suspense>
23
24            <main className="main-content">
25              <Suspense fallback={<PageSkeleton />}>
26                {children}
27              </Suspense>
28            </main>
29          </div>
30        </NotificationProvider>
31      </body>
32    </html>
33  );
34}
35
36export default RootLayout;

Step 3: Dashboard with Concurrent Features

1// app/dashboard/page.tsx (Server Component)
2import { Suspense } from 'react';
3import { TasksOverview } from '@/components/dashboard/TasksOverview';
4import { RecentActivity } from '@/components/dashboard/RecentActivity';
5import { PerformanceMetrics } from '@/components/dashboard/PerformanceMetrics';
6
7async function DashboardPage() {
8  // Parallel data fetching
9  const [tasksStats, recentTasks] = await Promise.all([
10    getTasksStatistics(),
11    getRecentTasks(10)
12  ]);
13
14  return (
15    <div className="dashboard">
16      <h1>Dashboard</h1>
17
18      {/* Fast-loading section */}
19      <TasksOverview stats={tasksStats} />
20
21      <div className="dashboard-grid">
22        {/* Streams independently */}
23        <Suspense fallback={<RecentActivitySkeleton />}>
24          <RecentActivity initialTasks={recentTasks} />
25        </Suspense>
26
27        {/* Can load later */}
28        <Suspense fallback={<MetricsSkeleton />}>
29          <PerformanceMetrics />
30        </Suspense>
31      </div>
32    </div>
33  );
34}
35
36export default DashboardPage;

Step 4: Interactive Components with new hooks

1// components/dashboard/RecentActivity.tsx (Client Component)
2'use client';
3
4import { useState, useTransition, useDeferredValue } from 'react';
5import { Task } from '@/types';
6
7interface RecentActivityProps {
8  initialTasks: Task[];
9}
10
11export function RecentActivity({ initialTasks }: RecentActivityProps) {
12  const [tasks, setTasks] = useState(initialTasks);
13  const [searchQuery, setSearchQuery] = useState('');
14  const [isPending, startTransition] = useTransition();
15
16  // Deferred value for search
17  const deferredSearchQuery = useDeferredValue(searchQuery);
18
19  // Filter tasks using deferredValue
20  const filteredTasks = tasks.filter(task =>
21    task.title.toLowerCase().includes(deferredSearchQuery.toLowerCase()) ||
22    task.description.toLowerCase().includes(deferredSearchQuery.toLowerCase())
23  );
24
25  const handleSearch = (query: string) => {
26    setSearchQuery(query);
27
28    // Use transition for less priority updates
29    startTransition(() => {
30      // Additional operations that can be interrupted
31      updateSearchAnalytics(query);
32    });
33  };
34
35  const refreshTasks = async () => {
36    startTransition(async () => {
37      const newTasks = await fetchRecentTasks();
38      setTasks(newTasks);
39    });
40  };
41
42  return (
43    <div className="recent-activity">
44      <div className="activity-header">
45        <h2>Recent Activity</h2>
46        <button
47          onClick={refreshTasks}
48          disabled={isPending}
49          className={isPending ? 'loading' : ''}
50        >
51          {isPending ? 'Refreshing...' : 'Refresh'}
52        </button>
53      </div>
54
55      <SearchInput
56        value={searchQuery}
57        onChange={handleSearch}
58        isPending={isPending}
59      />
60
61      <div className="tasks-list">
62        {filteredTasks.map(task => (
63          <TaskCard
64            key={task.id}
65            task={task}
66            onUpdate={setTasks}
67          />
68        ))}
69
70        {deferredSearchQuery && filteredTasks.length === 0 && (
71          <div className="no-results">
72            No results for "{deferredSearchQuery}"
73          </div>
74        )}
75      </div>
76    </div>
77  );
78}

Step 5: Advanced forms with Server Actions

1// components/forms/TaskForm.tsx (Client Component)
2'use client';
3
4import { useActionState, useFormStatus } from 'react';
5import { useId, useOptimistic } from 'react';
6import { createTask, updateTask } from '@/lib/actions';
7
8interface TaskFormProps {
9  task?: Task;
10  onSuccess?: () => void;
11}
12
13export function TaskForm({ task, onSuccess }: TaskFormProps) {
14  const formId = useId();
15  const titleId = useId();
16  const descriptionId = useId();
17
18  // useActionState for action state management
19  const [state, formAction] = useActionState(
20    task ? updateTask.bind(null, task.id) : createTask,
21    {
22      success: false,
23      errors: {},
24      data: task || null
25    }
26  );
27
28  // useOptimistic for instant feedback
29  const [optimisticState, addOptimistic] = useOptimistic(
30    state,
31    (currentState, optimisticUpdate) => ({
32      ...currentState,
33      ...optimisticUpdate,
34      optimistic: true
35    })
36  );
37
38  const handleSubmit = async (formData: FormData) => {
39    // Optimistic update
40    addOptimistic({
41      success: true,
42      data: {
43        id: task?.id || 'temp-' + Date.now(),
44        title: formData.get('title') as string,
45        description: formData.get('description') as string,
46        status: formData.get('status') as Task['status'],
47        priority: formData.get('priority') as Task['priority'],
48        createdAt: new Date(),
49        updatedAt: new Date()
50      }
51    });
52
53    // Execute actual action
54    await formAction(formData);
55
56    if (state.success) {
57      onSuccess?.();
58    }
59  };
60
61  return (
62    <form
63      id={formId}
64      action={handleSubmit}
65      className="task-form"
66    >
67      <div className="form-group">
68        <label htmlFor={titleId}>Task title</label>
69        <input
70          id={titleId}
71          name="title"
72          type="text"
73          required
74          defaultValue={optimisticState.data?.title || ''}
75          className={optimisticState.errors?.title ? 'error' : ''}
76        />
77        {optimisticState.errors?.title && (
78          <span className="error-message" role="alert">
79            {optimisticState.errors.title}
80          </span>
81        )}
82      </div>
83
84      <div className="form-group">
85        <label htmlFor={descriptionId}>Description</label>
86        <textarea
87          id={descriptionId}
88          name="description"
89          rows={4}
90          defaultValue={optimisticState.data?.description || ''}
91        />
92      </div>
93
94      <div className="form-group">
95        <label>Status</label>
96        <select name="status" defaultValue={optimisticState.data?.status || 'pending'}>
97          <option value="pending">Pending</option>
98          <option value="in_progress">In Progress</option>
99          <option value="completed">Completed</option>
100        </select>
101      </div>
102
103      <div className="form-group">
104        <label>Priority</label>
105        <select name="priority" defaultValue={optimisticState.data?.priority || 'medium'}>
106          <option value="low">Low</option>
107          <option value="medium">Medium</option>
108          <option value="high">High</option>
109        </select>
110      </div>
111
112      <FormSubmitButton />
113
114      {optimisticState.optimistic && (
115        <div className="optimistic-indicator">
116          Saving...
117        </div>
118      )}
119
120      {optimisticState.success && !optimisticState.optimistic && (
121        <div className="success-message">
122          Task has been {task ? 'updated' : 'created'}!
123        </div>
124      )}
125    </form>
126  );
127}
128
129// Component using useFormStatus
130function FormSubmitButton() {
131  const { pending } = useFormStatus();
132
133  return (
134    <button
135      type="submit"
136      disabled={pending}
137      className={pending ? 'loading' : ''}
138    >
139      {pending ? 'Saving...' : 'Save task'}
140    </button>
141  );
142}

Step 6: Server Actions

1// lib/actions.ts
2'use server';
3
4import { revalidatePath } from 'next/cache';
5import { redirect } from 'next/navigation';
6import { z } from 'zod';
7
8const TaskSchema = z.object({
9  title: z.string().min(1, 'Title is required').max(100, 'Title is too long'),
10  description: z.string().optional(),
11  status: z.enum(['pending', 'in_progress', 'completed']),
12  priority: z.enum(['low', 'medium', 'high']),
13  dueDate: z.string().optional().transform(str => str ? new Date(str) : undefined)
14});
15
16export async function createTask(prevState: any, formData: FormData) {
17  try {
18    const validatedFields = TaskSchema.safeParse({
19      title: formData.get('title'),
20      description: formData.get('description'),
21      status: formData.get('status'),
22      priority: formData.get('priority'),
23      dueDate: formData.get('dueDate')
24    });
25
26    if (!validatedFields.success) {
27      return {
28        success: false,
29        errors: validatedFields.error.flatten().fieldErrors,
30        data: null
31      };
32    }
33
34    const task = await db.tasks.create({
35      data: {
36        ...validatedFields.data,
37        id: generateId(),
38        createdAt: new Date(),
39        updatedAt: new Date()
40      }
41    });
42
43    // Revalidate cache
44    revalidatePath('/dashboard');
45    revalidatePath('/tasks');
46
47    return {
48      success: true,
49      errors: {},
50      data: task
51    };
52
53  } catch (error) {
54    return {
55      success: false,
56      errors: { _form: ['An error occurred while creating the task'] },
57      data: null
58    };
59  }
60}
61
62export async function updateTask(taskId: string, prevState: any, formData: FormData) {
63  try {
64    const validatedFields = TaskSchema.safeParse({
65      title: formData.get('title'),
66      description: formData.get('description'),
67      status: formData.get('status'),
68      priority: formData.get('priority'),
69      dueDate: formData.get('dueDate')
70    });
71
72    if (!validatedFields.success) {
73      return {
74        success: false,
75        errors: validatedFields.error.flatten().fieldErrors,
76        data: null
77      };
78    }
79
80    const task = await db.tasks.update({
81      where: { id: taskId },
82      data: {
83        ...validatedFields.data,
84        updatedAt: new Date()
85      }
86    });
87
88    revalidatePath(`/tasks/${taskId}`);
89    revalidatePath('/dashboard');
90
91    return {
92      success: true,
93      errors: {},
94      data: task
95    };
96
97  } catch (error) {
98    return {
99      success: false,
100      errors: { _form: ['An error occurred while updating the task'] },
101      data: null
102    };
103  }
104}

Step 7: Advanced Suspense Patterns

1// components/tasks/TasksList.tsx (Server Component)
2import { Suspense } from 'react';
3import { TaskCard } from './TaskCard';
4import { TasksFilter } from './TasksFilter';
5
6async function TasksList({ searchParams }: { searchParams: any }) {
7  return (
8    <div className="tasks-page">
9      <div className="tasks-header">
10        <h1>Tasks</h1>
11
12        {/* Filter doesn't require Suspense - loads quickly */}
13        <TasksFilter />
14      </div>
15
16      <div className="tasks-content">
17        {/* Main task list */}
18        <Suspense fallback={<TasksListSkeleton />}>
19          <TasksGrid searchParams={searchParams} />
20        </Suspense>
21
22        {/* Sidebar with additional info */}
23        <aside className="tasks-sidebar">
24          <Suspense fallback={<StatsSkeleton />}>
25            <TasksStatistics />
26          </Suspense>
27
28          <Suspense fallback={<ActivitySkeleton />}>
29            <RecentActivity />
30          </Suspense>
31        </aside>
32      </div>
33    </div>
34  );
35}
36
37// Component with long load time
38async function TasksGrid({ searchParams }: { searchParams: any }) {
39  // Simulate a long query
40  const tasks = await getFilteredTasks(searchParams);
41
42  return (
43    <div className="tasks-grid">
44      {tasks.map(task => (
45        <Suspense
46          key={task.id}
47          fallback={<TaskCardSkeleton />}
48        >
49          <TaskCard task={task} />
50        </Suspense>
51      ))}
52    </div>
53  );
54}

Step 8: Accessibility with useId

1// components/ui/FormField.tsx
2'use client';
3
4import { useId, forwardRef } from 'react';
5
6interface FormFieldProps {
7  label: string;
8  error?: string;
9  help?: string;
10  required?: boolean;
11  children: React.ReactNode;
12}
13
14export const FormField = forwardRef<HTMLDivElement, FormFieldProps>(
15  ({ label, error, help, required, children }, ref) => {
16    const fieldId = useId();
17    const errorId = useId();
18    const helpId = useId();
19
20    return (
21      <div ref={ref} className="form-field">
22        <label
23          htmlFor={fieldId}
24          className={required ? 'required' : ''}
25        >
26          {label}
27          {required && <span aria-hidden="true">*</span>}
28        </label>
29
30        <div className="field-input">
31          {React.cloneElement(children as React.ReactElement, {
32            id: fieldId,
33            'aria-describedby': [
34              help ? helpId : null,
35              error ? errorId : null
36            ].filter(Boolean).join(' ') || undefined,
37            'aria-invalid': error ? 'true' : undefined,
38            required
39          })}
40        </div>
41
42        {help && (
43          <div id={helpId} className="field-help">
44            {help}
45          </div>
46        )}
47
48        {error && (
49          <div
50            id={errorId}
51            className="field-error"
52            role="alert"
53            aria-live="polite"
54          >
55            {error}
56          </div>
57        )}
58      </div>
59    );
60  }
61);
62
63FormField.displayName = 'FormField';

Step 9: Performance Monitoring

1// components/providers/PerformanceProvider.tsx
2'use client';
3
4import { createContext, useContext, useEffect, useState } from 'react';
5
6interface PerformanceMetrics {
7  renderTime: number;
8  interactionTime: number;
9  suspenseTime: number;
10}
11
12const PerformanceContext = createContext<{
13  metrics: PerformanceMetrics;
14  recordMetric: (type: keyof PerformanceMetrics, time: number) => void;
15}>({
16  metrics: { renderTime: 0, interactionTime: 0, suspenseTime: 0 },
17  recordMetric: () => {}
18});
19
20export function PerformanceProvider({ children }: { children: React.ReactNode }) {
21  const [metrics, setMetrics] = useState<PerformanceMetrics>({
22    renderTime: 0,
23    interactionTime: 0,
24    suspenseTime: 0
25  });
26
27  const recordMetric = (type: keyof PerformanceMetrics, time: number) => {
28    setMetrics(prev => ({
29      ...prev,
30      [type]: time
31    }));
32
33    // Send metrics to analytics system
34    if (time > getThreshold(type)) {
35      analytics.track('performance_issue', {
36        type,
37        time,
38        url: window.location.pathname
39      });
40    }
41  };
42
43  useEffect(() => {
44    // Observe Web Vitals
45    const observer = new PerformanceObserver((list) => {
46      for (const entry of list.getEntries()) {
47        if (entry.entryType === 'measure') {
48          recordMetric('renderTime', entry.duration);
49        }
50      }
51    });
52
53    observer.observe({ entryTypes: ['measure'] });
54
55    return () => observer.disconnect();
56  }, []);
57
58  return (
59    <PerformanceContext.Provider value={{ metrics, recordMetric }}>
60      {children}
61    </PerformanceContext.Provider>
62  );
63}
64
65export const usePerformance = () => useContext(PerformanceContext);
66
67function getThreshold(type: keyof PerformanceMetrics): number {
68  const thresholds = {
69    renderTime: 100,      // 100ms
70    interactionTime: 50,  // 50ms
71    suspenseTime: 1000    // 1s
72  };
73  return thresholds[type];
74}

Project Summary

This project demonstrates:

  1. Concurrent Features: useTransition, useDeferredValue for responsive UI
  2. Form Handling: useActionState, useFormStatus with Server Actions
  3. Accessibility: useId for unique identifiers
  4. Optimistic Updates: useOptimistic for instant feedback
  5. Server Components: Server-side rendering with parallel data fetching
  6. Suspense Patterns: Hierarchical loading with streaming
  7. Performance: Monitoring and performance optimization
  8. Error Handling: Granular Error Boundaries
  9. Type Safety: Full TypeScript utilization

Next steps for extension:

  1. Add real-time synchronization with WebSockets
  2. Implement offline support with Service Workers
  3. Add advanced filters and sorting
  4. Create a user permissions system
  5. Integrate with push notification system
  6. Add data export to various formats
  7. Implement dark mode with CSS-in-JS
  8. Create a mobile app with React Native

This project is a solid foundation for building modern React applications utilizing the latest framework features.

Go to CodeWorlds→