Skip to main content
Web MobileIntermediate18 min readUpdated March 2025

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.

tsx
'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:

tsx
'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.

tsx
// 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:

typescript
// 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