Het patroon

Bewerken is technisch lastiger dan toevoegen. Bij een nieuwe workout begint je form leeg. Bij bewerken moet hij voorgevuld zijn met de huidige waarden, anders typt de gebruiker alles opnieuw.

Drie stukken

  1. Een knop "Bewerken" per item.
  2. Een EditWorkoutForm dat een workout-object als prop krijgt en de inputs voorvult.
  3. Een handleUpdate die PATCH stuurt en de state in de lijst bijwerkt.

Bewerk-knop in Workout

Naast de Verwijder-knop komt een Bewerk-knop. Welke workout op dat moment bewerkt wordt, beheer je in de parent (WorkoutList):

// frontend/src/components/Workout.jsx

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

      <button onClick={() => onEdit(workout)}>Bewerken</button>
      <button onClick={() => onDelete(workout._id)}>Verwijderen</button>
    </div>
  );
}
Let op
  • onEdit(workout) — we geven het hele object door (niet alleen het id) zodat de parent het kan voorvullen.
  • onDelete(workout._id) — daar is alleen het id genoeg.

EditWorkoutForm component

Maak frontend/src/components/EditWorkoutForm.jsx. Hij krijgt de te bewerken workout binnen als prop en gebruikt die als initial value voor zijn state:

// frontend/src/components/EditWorkoutForm.jsx

import { useState } from 'react';

function EditWorkoutForm({ workout, onUpdated, onCancel }) {
  const [title, setTitle] = useState(workout.title);
  const [reps, setReps] = useState(workout.reps);
  const [load, setLoad] = useState(workout.load);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();

    const updates = {
      title,
      reps: Number(reps),
      load: Number(load)
    };

    const response = await fetch(
      `http://localhost:4000/api/workouts/${workout._id}`,
      {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updates)
      }
    );

    const data = await response.json();

    if (!response.ok) {
      setError(data.error);
      return;
    }

    onUpdated(data); // geef de bijgewerkte workout terug aan parent
  };

  return (
    <form className="edit-form" onSubmit={handleSubmit}>
      <h3>Workout bewerken</h3>

      <label>Titel:</label>
      <input value={title} onChange={(e) => setTitle(e.target.value)} />

      <label>Reps:</label>
      <input type="number" value={reps} onChange={(e) => setReps(e.target.value)} />

      <label>Load (kg):</label>
      <input type="number" value={load} onChange={(e) => setLoad(e.target.value)} />

      <button type="submit">Opslaan</button>
      <button type="button" onClick={onCancel}>Annuleren</button>

      {error && <p className="error">{error}</p>}
    </form>
  );
}

export default EditWorkoutForm;
De truc met voorvullen
  • useState(workout.title) — de initial value van useState is het meegekregen veld. Dat gebeurt één keer bij het mounten.
  • Daarna leeft de state los van de prop — typt de gebruiker iets nieuws, dan blijft dat in state staan.
  • type="button" bij Annuleren — anders submit hij ook de form.

WorkoutList: form tonen op aanvraag

De parent houdt bij wélke workout op dit moment bewerkt wordt. Is dat null, geen form. Anders tonen we het form bovenaan:

// frontend/src/components/WorkoutList.jsx

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

function WorkoutList() {
  const [workouts, setWorkouts] = useState([]);
  const [editing, setEditing] = useState(null); // welke workout?

  // ... fetch in useEffect ...

  const handleUpdated = (updatedWorkout) => {
    setWorkouts(workouts.map(w =>
      w._id === updatedWorkout._id ? updatedWorkout : w
    ));
    setEditing(null);
  };

  return (
    <div>
      {editing && (
        <EditWorkoutForm
          workout={editing}
          onUpdated={handleUpdated}
          onCancel={() => setEditing(null)}
        />
      )}

      {workouts.map(workout => (
        <Workout
          key={workout._id}
          workout={workout}
          onEdit={setEditing}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}
Wat gebeurt hier?
  • editing is een workout-object of null.
  • {editing && ...} — alleen renderen als er iets te bewerken is.
  • onEdit={setEditing} — de Bewerk-knop zet de te bewerken workout in state.
  • setEditing(null) bij Annuleren of na succesvol opslaan — sluit het form.

State bijwerken na PATCH

Na een succesvolle PATCH krijg je de bijgewerkte workout terug. Je vervangt het oude exemplaar in je array met .map():

setWorkouts(workouts.map(w =>
  w._id === updatedWorkout._id ? updatedWorkout : w
));
Hoe werkt dit?
  • .map() loopt door elke workout.
  • Voor de bijgewerkte workout (zelfde id) gebruiken we het nieuwe object.
  • Alle andere workouts blijven onveranderd.
  • Resultaat: nieuwe array met één gewijzigd item — React rendert die ene rij opnieuw.

Geen volledige refetch nodig

Je hoeft niet opnieuw GET /api/workouts te doen. Je hebt het bijgewerkte object al — dat gebruik je gewoon.

Veelgemaakte fouten

1. Form niet voorvullen

Je geeft de prop wel mee maar gebruikt hem niet als initial state. De gebruiker ziet een leeg form en moet alles opnieuw typen.

2. useState(workout.title) en verwachten dat hij meeloopt

De initial value wordt maar één keer gebruikt. Verandert de prop later? Dan blijft state op de oude waarde. Voor dit patroon (één form open per keer) prima — voor andere gevallen kun je een key op de form zetten.

3. type="submit" op de Annuleer-knop laten staan

Standaard is een knop in een form van het type submit. Annuleer-knop submit dan óók de form. Zet expliciet type="button".

4. Hele lijst opnieuw fetchen na PATCH

Werkt wel maar is traag en knipperig. Update gewoon dat ene item in state.

Volgende Stap

CRUD volledig rond. Nu wordt het tijd om de app te beveiligen — eerst aan de backend-kant. Daar maak je user accounts, login en JWT-tokens.

Authentication (Backend) →

Register, login en JWT in je Express backend