DELETE patroon
De backend heeft al een DELETE /api/workouts/:id route. Op de frontend wil je per workout een
knop "Verwijderen" tonen. Bij een klik:
Drie dingen gebeuren
- Stuur een
fetchmetmethod: 'DELETE'naar de juiste URL. - Bij succes: verwijder de workout uit je state.
- De UI updatet automatisch — geen page-refresh nodig.
Workout met verwijder-knop
Het Workout component krijgt een knop. Hij weet niet zelf hoe te verwijderen — dat geef je via
een prop door:
// frontend/src/components/Workout.jsx
function Workout({ workout, onDelete }) {
return (
<div className="workout">
<h3>{workout.title}</h3>
<p>Reps: {workout.reps}</p>
<p>Load: {workout.load} kg</p>
<button onClick={() => onDelete(workout._id)}>
Verwijderen
</button>
</div>
);
}
export default Workout;
onDelete als prop?
- De parent (
WorkoutList) heeft de state met alle workouts. Alleen die kan een workout eruit halen. Workoutblijft "dom" — hij weet alleen dát er gebeld moet worden, niet hoe.() => onDelete(workout._id)— arrow-functie zodat je het ID kunt meegeven.
WorkoutList: fetch + state updaten
De parent definieert de delete-functie en geeft hem door:
// frontend/src/components/WorkoutList.jsx
const handleDelete = async (id) => {
const response = await fetch(`http://localhost:4000/api/workouts/${id}`, {
method: 'DELETE'
});
if (!response.ok) {
console.error('Verwijderen mislukt');
return;
}
// Verwijder uit state
setWorkouts(workouts.filter(w => w._id !== id));
};
return (
<div className="workout-list">
{workouts.map(workout => (
<Workout
key={workout._id}
workout={workout}
onDelete={handleDelete}
/>
))}
</div>
);
workouts.filter(w => w._id !== id)geeft een nieuwe array terug zonder de verwijderde workout.- State wijzig je nooit direct (geen
workouts.splice(...)!) — altijd viasetWorkouts(...)met een nieuwe array. - React ziet "nieuwe array" en rendert opnieuw. Gebruiker ziet de workout verdwijnen.
Filter patroon
Filteren doe je frontend-side: je hebt alle workouts al in state, dus geen extra fetch nodig. Je houdt bij welke filter actief is en mapt alleen over de items die voldoen.
Voorbeeld: filter op spiergroep
Stel: je hebt je Workout schema uitgebreid met een veld muscleGroup (enum:
chest, back, legs, arms). Je wil knoppen waarmee je
de lijst filtert.
// In de backend Workout schema (voorbeeld)
muscleGroup: {
type: String,
enum: ['chest', 'back', 'legs', 'arms'],
required: true
}
MuscleGroupFilter component
Een knoppenrij. Hij onthoudt niets zelf — de actieve filter staat in de parent.
// frontend/src/components/MuscleGroupFilter.jsx
function MuscleGroupFilter({ active, onChange }) {
const groups = ['alle', 'chest', 'back', 'legs', 'arms'];
return (
<div className="filter">
{groups.map(group => (
<button
key={group}
className={active === group ? 'active' : ''}
onClick={() => onChange(group)}
>
{group}
</button>
))}
</div>
);
}
export default MuscleGroupFilter;
active— welke filter is nu geselecteerd. Komt van de parent.onChange— callback naar de parent als de gebruiker een knop klikt.className={active === group ? 'active' : ''}— geeft de actieve knop een styling-haakje.
Filter toepassen in WorkoutList
De parent houdt de actieve filter bij en past .filter() toe vóór het mappen:
// frontend/src/components/WorkoutList.jsx
import { useState, useEffect } from 'react';
import Workout from './Workout';
import MuscleGroupFilter from './MuscleGroupFilter';
function WorkoutList() {
const [workouts, setWorkouts] = useState([]);
const [activeGroup, setActiveGroup] = useState('alle');
// ... useEffect met fetch zoals in vorige stap ...
const visibleWorkouts = activeGroup === 'alle'
? workouts
: workouts.filter(w => w.muscleGroup === activeGroup);
return (
<div>
<MuscleGroupFilter
active={activeGroup}
onChange={setActiveGroup}
/>
<div className="workout-list">
{visibleWorkouts.map(workout => (
<Workout
key={workout._id}
workout={workout}
onDelete={handleDelete}
/>
))}
</div>
</div>
);
}
activeGroupbegint op'alle'zodat alles zichtbaar is.visibleWorkoutsis een afgeleide variabele — geen aparte state. Die berekent React opnieuw bij elke render.onChange={setActiveGroup}— je geeft de setter direct mee als callback.
Geen tweede fetch nodig
Je hoeft niet opnieuw naar de backend te gaan voor een filter. De data is er al, je toont gewoon een subset.
Veelgemaakte fouten
1. State direct muteren
workouts.splice(index, 1) wijzigt de bestaande array. React merkt het niet, UI updatet
niet. Gebruik altijd filter(...) of map(...) die nieuwe arrays teruggeven.
2. onClick={onDelete(id)} zonder arrow
Dat roept onDelete direct aan tijdens het renderen — niet bij de klik. Wikkel hem in een
arrow: onClick={() => onDelete(id)}.
3. Filter terug naar de backend sturen
Voor een handvol enum-waarden is .filter() in de browser sneller en eenvoudiger. Pas bij
duizenden items wordt backend-filtering nuttig.
4. Knop zonder visueel verschil als hij actief is
De gebruiker weet niet welke filter aanstaat. Gebruik een active className en style hem
anders.
Volgende Stap
Toevoegen ✓, verwijderen ✓, filteren ✓. Nu nog de U van CRUD: bestaande data bewerken.
Form voorvullen met huidige waarden en een PATCH-request sturen