React & Next.js Fundamentals
Build modern user interfaces with React components and hooks, then structure a full application with Next.js routing, server components and API routes.
Components and State
A React component is a function that returns UI. Props pass data down; state (via `useState`) holds data that changes over time. Updating state re-renders the component.
'use client';
import { useState } from 'react';
interface CounterProps {
initial?: number;
}
export default function Counter({ initial = 0 }: CounterProps) {
const [count, setCount] = useState(initial);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<button onClick={() => setCount(initial)}>Reset</button>
</div>
);
}Fetching Data with useEffect
Effects run after render and are the place for subscriptions and client-side fetching. Always clean up and handle loading and error states:
'use client';
import { useEffect, useState } from 'react';
interface Post { id: number; title: string }
export default function Posts() {
const [posts, setPosts] = useState<Post[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch('https://jsonplaceholder.typicode.com/posts?_limit=5', { signal: controller.signal })
.then((r) => { if (!r.ok) throw new Error('Request failed'); return r.json(); })
.then(setPosts)
.catch((e) => { if (e.name !== 'AbortError') setError(e.message); })
.finally(() => setLoading(false));
return () => controller.abort();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p role="alert">{error}</p>;
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}Next.js App Router
Next.js adds file-based routing, server rendering and optimizations on top of React. Files in `app/` become routes, and components are server components by default, which means less JavaScript is sent to the browser.
// app/blog/page.tsx -> /blog
interface Post { id: number; title: string }
export default async function BlogPage() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5', {
next: { revalidate: 60 },
});
const posts: Post[] = await res.json();
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
</main>
);
}API Routes
Route handlers let the same project expose back-end endpoints:
// app/api/health/route.ts -> GET /api/health
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ status: 'ok', time: new Date().toISOString() });
}Key Takeaways
- Components return UI; state changes trigger re-renders.
- Fetch in effects with cleanup, or on the server with async components.
- Next.js maps files in app/ to routes and defaults to server components.
- Route handlers provide back-end endpoints in the same codebase.
Contact Us
Have a question or feedback? Fill out the form below or reach us directly at support@nvaitraining.com