Content
Difficulty: Easy Concepts: useRef, DOM manipulation, focus management, accessibility
Build a comment form that manages focus automatically as the user interacts with it.
Requirements:
CommentForm component with a <textarea> and a Submit buttonconsole.log the comment text), show a confirmation message and move focus to it — use a <p> with role="status" for the confirmation so screen reader users are notifiedRequirements for refs:
document.querySelector or any other direct DOM query — all DOM access must go through refsHints:
useEffect with an empty dependency array runs once on mount — use it for the initial focus callsetTimeout can be stored in a ref so you can clear it if the component unmounts before it firestabIndex={-1} on the confirmation paragraph makes it programmatically focusable without adding it to the tab orderDifficulty: Medium Concepts: React Server Components, async components, Next.js App Router, Server Functions
You have a bloated Client Component that fetches a list of blog posts using useEffect. Refactor it into a Server Component and wire up a Server Function to handle marking a post as read.
Starting point:
'use client';
import { useState, useEffect } from 'react';
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
export default function PostList() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch('<https://jsonplaceholder.typicode.com/posts?_limit=10>', {
signal: controller.signal,
})
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((data) => {
setPosts(data);
setLoading(false);
})
.catch((err) => {
if (err.name !== 'AbortError') {
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, []);
if (loading) return <p>Loading posts...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
);
}
Part 1 — Refactor to a Server Component:
'use client', useState, and useEffect entirelyasync and fetch directly in the component bodyPart 2 — Add a Server Function:
markAsRead Server Function in a separate actions.ts file marked with 'use server'postId and console.log(Post ${postId} marked as read) — JSONPlaceholder doesn't persist changes so logging is fineMarkAsReadButton Client Component that accepts a postId prop and calls markAsRead when clickeduseTransition so the button shows a pending state while the function runsThe final file structure should look like this:
app/
└── posts/
├── page.tsx ← async Server Component
├── MarkAsReadButton.tsx ← Client Component ('use client')
└── actions.ts ← Server Function ('use server')
Hints:
MarkAsReadButton needs 'use client' because it uses useTransition and onClick. page.tsx does notisPending from useTransition can disable the button and show "Marking..." while the Server Function runsThe HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

Built with ❤️ by the HackYourFuture community · Thank you, contributors
Found a mistake or have a suggestion? Let us know in the feedback form.