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

  1. Stuur een fetch met method: 'DELETE' naar de juiste URL.
  2. Bij succes: verwijder de workout uit je state.
  3. 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;
Waarom onDelete als prop?
  • De parent (WorkoutList) heeft de state met alle workouts. Alleen die kan een workout eruit halen.
  • Workout blijft "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>
);
De truc met filter
  • workouts.filter(w => w._id !== id) geeft een nieuwe array terug zonder de verwijderde workout.
  • State wijzig je nooit direct (geen workouts.splice(...)!) — altijd via setWorkouts(...) 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;
Wat gebeurt hier?
  • 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>
  );
}
Wat gebeurt hier?
  • activeGroup begint op 'alle' zodat alles zichtbaar is.
  • visibleWorkouts is 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.

Bewerken (PATCH) →

Form voorvullen met huidige waarden en een PATCH-request sturen