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
- Een knop "Bewerken" per item.
- Een
EditWorkoutFormdat een workout-object als prop krijgt en de inputs voorvult. - Een
handleUpdatediePATCHstuurt 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>
);
}
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;
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>
);
}
editingis een workout-object ofnull.{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
));
.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.
Register, login en JWT in je Express backend