Het patroon

We willen alle workouts uit de backend ophalen en tonen. Daarvoor splitsen we de UI in twee componenten:

Twee componenten, twee taken

  • WorkoutList — haalt de array op en mapt erover.
  • Workout — toont één workout. Krijgt data via props.

Het rootbestand App.jsx rendert alleen de WorkoutList en doet zelf geen fetch.

Maak in frontend/src/ een nieuwe map components/ aan. Daarin maak je twee bestanden:

frontend/src/components/
├── Workout.jsx
└── WorkoutList.jsx

Workout component

Begin met de kleinste: een component dat één workout toont. Hij krijgt zijn data via props.

// frontend/src/components/Workout.jsx

function Workout({ workout }) {
  return (
    <div className="workout">
      <h3>{workout.title}</h3>
      <p>Reps: {workout.reps}</p>
      <p>Load: {workout.load} kg</p>
    </div>
  );
}

export default Workout;
Wat gebeurt hier?
  • { workout } — destructuring van de props. workout is het object dat we van de parent meekrijgen.
  • Geen state, geen fetch — alleen weergeven.
  • export default — zodat WorkoutList hem kan importeren.

Klein, dom, herbruikbaar

Een component dat alleen weergeeft wat hij krijgt heet een presentational component. Makkelijk te lezen, makkelijk te testen.

WorkoutList component

Deze component is verantwoordelijk voor het ophalen van de data en het mappen naar Workout-componenten.

// frontend/src/components/WorkoutList.jsx

import { useEffect, useState } from 'react';
import Workout from './Workout';

function WorkoutList() {
  const [workouts, setWorkouts] = useState([]);

  useEffect(() => {
    const fetchWorkouts = async () => {
      const response = await fetch('http://localhost:4000/api/workouts');
      const data = await response.json();
      setWorkouts(data);
    };

    fetchWorkouts();
  }, []);

  return (
    <div className="workout-list">
      {workouts.map(workout => (
        <Workout key={workout._id} workout={workout} />
      ))}
    </div>
  );
}

export default WorkoutList;
Wat gebeurt hier?
  • useState([]) — start met lege array zodat .map() nooit crasht.
  • useEffect(() => {...}, []) — met lege dependency array draait dit één keer als de component op het scherm verschijnt.
  • fetchWorkouts staat binnen useEffect — dat is netter dan een aparte async functie buiten de hook.
  • setWorkouts(data) — als de data binnen is, gaat hij in state. React rendert opnieuw.
  • key={workout._id} — verplicht bij .map() in React. Gebruik altijd het MongoDB _id.
  • workout={workout} — we geven het hele object door als prop, niet losse velden.

Waarom geen async direct in useEffect?

useEffect(async () => ...) mag niet — useEffect verwacht óf niets óf een cleanup-functie terug, geen Promise. Daarom maken we de async functie er binnenin en roepen die direct aan.

App.jsx aansluiten

App.jsx blijft simpel: hij importeert en rendert WorkoutList, meer niet.

// frontend/src/App.jsx

import WorkoutList from './components/WorkoutList';

function App() {
  return (
    <div className="App">
      <h1>Workouts</h1>
      <WorkoutList />
    </div>
  );
}

export default App;

Test het in de browser

Ga naar http://localhost:5173. Je ziet je workouts uit MongoDB onder elkaar staan.

Geen workouts? Voeg er een paar toe via Postman (POST naar /api/workouts) en refresh.

Waarom in losse componenten splitsen?

Je had alles ook in App.jsx kunnen proppen. Dat werkt — voor één scherm. Maar zodra je gaat uitbreiden met formulieren, filters en bewerk-knoppen, wordt App.jsx één lange brei. Dan vind je niets meer terug.

Eén component, één verantwoordelijkheid

  • Workout weet hoe je één workout toont.
  • WorkoutList weet hoe je de lijst ophaalt en weergeeft.
  • App weet hoe je de pagina opbouwt.

Wijzig je iets aan hoe één workout eruitziet? Dan open je alleen Workout.jsx — niet een bestand van 400 regels.

Samenvatting

Je hebt nu:

  • Een Workout component dat één workout toont via props.
  • Een WorkoutList component dat de array ophaalt met useEffect + fetch en erover mapt.
  • Een schone App.jsx die alleen de lijst rendert.

Volgende Stap

Lijst staat. Maar wat zien gebruikers terwijl de fetch onderweg is, of als de backend offline is?

Loading & Error States →

Feedback geven tijdens het laden en bij fouten