Week 9

useReducer

Context API

Error Boundaries

React Hook Form

Practice

Assignment

Front end Track

Week 9 Assignment: Upgrade the Portfolio Contact Form

Focus: React Hook Form, useReducer, Context API, Error Boundaries

Description

Your portfolio has been live since week 6 with routing, Tailwind styling, and a deployed URL on Vercel. The contact form you built then uses useState with manual validation — which works, but starts to show its limits when you add fields, more validation rules, or need to surface server-side errors cleanly.

This week you apply what you've learned to upgrade that form using React Hook Form, protect the portfolio against unexpected rendering failures with Error Boundaries, and optionally lift shared UI state into Context. When you're done, the portfolio's contact form will be production-grade: validated, accessible, and resilient.

What you're building on

This assignment assumes your portfolio is currently the deployed Vercel application from week 6, with:

If your week 6 project differs from this, adapt the requirements to fit what you have.

Requirements

1. Upgrade the Contact Form with React Hook Form

Install React Hook Form:

npm install react-hook-form

Open your ContactPage and rewrite the form using useForm. The goal is to delete every useState call that manages a form field, every onChange handler, and every manual validation check — and replace them with the React Hook Form equivalent.

Field requirements:

Your contact form must have at least three fields. If yours currently has fewer, add them now:

Field Type Validation
name text Required, minimum 2 characters
email text Required, valid email format (use a pattern rule)
message textarea Required, minimum 20 characters

Register each field using {...register('fieldName', validationRules)}. No value or onChange props should remain on any input or textarea.

Error messages:

Display each field's error message directly below its input:

{errors.name && (
  <p role="alert" className="text-sm text-red-600 mt-1">
    {errors.name.message}
  </p>
)}

Add aria-invalid={errors.fieldName ? 'true' : 'false'} to each field so screen readers announce invalid state.

Submission:

Keep your existing simulated submission (the setTimeout or Promise you already have). After a successful submission:

If the submission fails, use setError('root', { message: '...' }) to display a form-level error below the submit button, separate from field-level errors.

Submit button state:

Disable the button while isSubmitting is true. Use isSubmitting from formState — do not add a separate loading state.

<button type="submit" disabled={isSubmitting}>
  {isSubmitting ? 'Sending...' : 'Send message'}
</button>

2. Add Error Boundaries to the Portfolio

Install react-error-boundary:

npm install react-error-boundary

Create a reusable fallback component in src/components/SectionError.tsx:

interface SectionErrorProps {
  error: Error;
  resetErrorBoundary: () => void;
}

export function SectionError({ error, resetErrorBoundary }: SectionErrorProps) {
  return (
    <div role="alert" className="rounded-lg border border-red-200 bg-red-50 p-6 text-center">
      <p className="font-medium text-red-800">This section failed to load</p>
      <p className="mt-1 text-sm text-red-600">{error.message}</p>
      <button
        onClick={resetErrorBoundary}
        className="mt-4 rounded-md bg-red-100 px-4 py-2 text-sm font-medium text-red-800 hover:bg-red-200"
      >
        Try again
      </button>
    </div>
  );
}

Wrap each page component in App.tsx in its own <ErrorBoundary>:

import { ErrorBoundary } from 'react-error-boundary';
import { SectionError } from './components/SectionError';

export default function App() {
  return (
    <>
      <Navigation />
      <main>
        <Routes>
          <Route
            path="/"
            element={
              <ErrorBoundary FallbackComponent={SectionError}>
                <HomePage />
              </ErrorBoundary>
            }
          />
          <Route
            path="/projects"
            element={
              <ErrorBoundary FallbackComponent={SectionError}>
                <ProjectsPage />
              </ErrorBoundary>
            }
          />
          <Route
            path="/contact"
            element={
              <ErrorBoundary FallbackComponent={SectionError}>
                <ContactPage />
              </ErrorBoundary>
            }
          />
          <Route path="*" element={<NotFoundPage />} />
        </Routes>
      </main>
    </>
  );
}

Add a comment above the route definitions in App.tsx explaining in your own words:

3. Lift Dark Mode into Context (Extension)

Your dark mode toggle currently lives in Navigation and manages its own useState. That works when the toggle only needs to affect itself. But if you ever want a component deeper in the page — say, a project card or a form input — to know the current theme, you'd have to prop-drill it down.

This extension upgrades the toggle to use Context so any component can read the current theme without receiving it as a prop.

Requirements:

Hints


Submission

Follow the Assignment submission guide to learn how to submit the assignment


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.