Week 10

Refs

Custom hooks

React 19

React Server Components

Practice

Assignment

Front end Track

Week 10 Assignment: Polish the Portfolio with Refs, Hooks, and React 19

Focus: useRef, custom hooks, useTransition

Description

Your portfolio is deployed, functional, and styled. This week's additions are smaller in scope than previous weeks โ€” but they're the kind of details that separate a project that works from one that feels considered. Auto-focusing form fields, syncing the browser tab title to the current page, and simplifying loading state with React 19's useTransition are all low-effort, high-polish improvements that make the portfolio noticeably more professional.

What you're building on

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

Requirements

1. Auto-focus the contact form on mount

When a user navigates to /contact, the first input field โ€” the name field โ€” should be focused automatically so they can start typing without clicking first. This is a small accessibility improvement: users navigating by keyboard or arriving with clear intent don't have to take an extra action.

In your ContactPage, create a ref for the name input and focus it when the component mounts:

import { useRef, useEffect } from 'react';

const nameRef = useRef<HTMLInputElement>(null);

useEffect(() => {
  nameRef.current?.focus();
}, []);

React Hook Form registers inputs using its own ref internally, so attaching your own ref requires a small adjustment. Use useForm's register alongside a manual ref callback, or switch the name field to use Controller so you can attach your own ref directly.

The cleanest approach is to get the ref from register using the returned ref property:

const { ref: nameRegisterRef, ...nameRegisterRest } = register('name', {
  required: 'Name is required',
  minLength: { value: 2, message: 'Name is too short' },
});

// Combine React Hook Form's ref with your own
const setNameRef = (el: HTMLInputElement | null) => {
  nameRegisterRef(el);  // React Hook Form needs this
  (nameRef as React.MutableRefObject<HTMLInputElement | null>).current = el;
};

Then on the input:

<input ref={setNameRef} {...nameRegisterRest} />

Confirm it works: navigate to /contact and check that the cursor is in the name field immediately, without clicking.

<aside> ๐Ÿ’ก

Accessibility note

Auto-focusing is appropriate here because the entire purpose of the /contact page is to fill in the form โ€” unlike a general page where focus-on-mount would be disorienting. Always consider whether auto-focus helps or distracts before applying it.

</aside>

2. Sync the browser tab title per page with a custom hook

Right now, every page of your portfolio probably shows the same tab title. Update it per page so that navigating to /projects shows "Projects โ€” Your Name" in the browser tab, and so on.

Create a custom hook in src/hooks/useDocumentTitle.ts:

import { useEffect } from 'react';

export function useDocumentTitle(title: string) {
  useEffect(() => {
    const previous = document.title;
    document.title = title;

    // Restore the original title when the component unmounts
    return () => {
      document.title = previous;
    };
  }, [title]);
}

Call it at the top of each page component:

// HomePage.tsx
useDocumentTitle('Home โ€” Your Name');

// ProjectsPage.tsx
useDocumentTitle('Projects โ€” Your Name');

// ContactPage.tsx
useDocumentTitle('Contact โ€” Your Name');

Open each page in the browser and confirm the tab title updates as you navigate. Then open a new tab on any page and confirm the title is correct from the first render โ€” not just after navigating from another page.

3. Replace the manual loading state on the contact form with useTransition

In week 9 you used isSubmitting from React Hook Form's formState to disable the submit button while the form is processing. React 19's useTransition gives you a complementary tool for the same pattern โ€” and it can wrap any async operation, not just form submission.

This is an opportunity to see both approaches and understand the difference. isSubmitting from React Hook Form tracks the state of the form's own async submission. useTransition's isPending tracks any async operation you explicitly start with startTransition.

Wrap your onSubmit handler with startTransition:

import { useTransition } from 'react';

const [isPending, startTransition] = useTransition();

function onSubmit(data: ContactFormData) {
  startTransition(async () => {
    // Your existing simulated API call
    await new Promise((resolve) => setTimeout(resolve, 1500));
    reset();
    navigate('/');
  });
}

Update the submit button to use isPending alongside (or instead of) isSubmitting:

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

Confirm the button disables correctly during submission and that the form resets and redirects after a successful submission.

Note: In a real production app you would pick one or the other โ€” isSubmitting from React Hook Form is usually sufficient for forms. The goal here is to practise useTransition in a realistic context, not to use both simultaneously in production code.

Acceptance Criteria

Before submitting, run through this checklist:

Auto-focus

Document title

useTransition

General

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.