Réaliser un CRUD (« Create, Read, Update, Delete ») dans une application React en utilisant Axios.
Table des matières : [Masquer]
📌 Installation de l’API
- Installez ou réactivez l’API Laravel « Tuto-Laravel-API » via le lien GitHub indiqué plus haut.
- Créez une nouvelle base de données.
- Configurez le fichier
.env
pour connecter votre base de données à votre API. - Exécutez
composer update
pour installer les dépendances. - Exécutez
php artisan migrate
pour créer les tables.
📌 Installation de l’application React.js
npx create-react-app nom-de-votre-projet
cd nom-de-votre-projet
Installez les dépendances :
npm i -s react-router-dom sass react-bootstrap bootstrap axios
Importez Bootstrap dans index.js
:
import 'bootstrap/dist/css/bootstrap.min.css';
Lancez votre projet :
npm start
📌 Création du système de routage
Créez le dossier pages
dans src
avec les fichiers suivants :
- src/pages/clubs/Clubs.jsx - src/pages/clubs/AddClub.jsx - src/pages/clubs/EditClub.jsx - src/pages/players/Players.jsx - src/pages/players/AddPlayer.jsx - src/pages/players/EditPlayer.jsx - src/pages/Home.jsx
Une fois ces fichiers crées, paramétrez vos routes dans le fichier app.js :
// Importation des bibliothèques React et React Router
import React from 'react';
import { BrowserRouter, Routes, Route } from "react-router-dom";
// Importation des composants/pages pour la navigation
import Clubs from './pages/clubs/Clubs';
import AddClub from './pages/clubs/AddClub';
import EditClub from './pages/clubs/EditClub';
import Players from './pages/players/Players';
import AddPlayer from './pages/players/AddPlayer';
import EditPlayer from './pages/players/EditPlayer';
import Home from "./pages/Home";
// Définition du composant principal App
function App() {
return (
// BrowserRouter est le conteneur principal qui permet la gestion du routage
<BrowserRouter>
{/* Routes englobe toutes les routes définies pour l'application */}
<Routes>
{/* Route principale : affichage de la page d'accueil */}
<Route path="/" element={<Home />} />
{/* Routes pour la gestion des clubs */}
<Route path="/clubs" element={<Clubs />} /> {/* Liste des clubs */}
<Route path="/clubs/add" element={<AddClub />} /> {/* Ajout d'un club */}
<Route path="/clubs/edit/:club" element={<EditClub />} /> {/* Modification d'un club */}
{/* Routes pour la gestion des joueurs */}
<Route path="/players" element={<Players />} /> {/* Liste des joueurs */}
<Route path="/players/add" element={<AddPlayer />} /> {/* Ajout d'un joueur */}
<Route path="/players/edit/:player" element={<EditPlayer />} /> {/* Modification d'un joueur */}
{/* Route par défaut : redirige vers Home si aucune route ne correspond */}
<Route path="*" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
// Exportation du composant App pour qu'il puisse être utilisé ailleurs dans l'application
export default App;
📌 Création des composants
Créez un fichier Menu.jsx
dans src/components/
et intégrez-le dans Home.jsx
.
// Importation des bibliothèques React et des composants Bootstrap pour la barre de navigation
import React from "react";
import Container from "react-bootstrap/Container";
import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar";
import NavDropdown from "react-bootstrap/NavDropdown";
// Définition du composant Menu
const Menu = () => {
return (
<div>
{/* Création de la barre de navigation Bootstrap */}
<Navbar bg="light" expand="lg">
<Container fluid>
{/* Bouton pour afficher/cacher le menu en mode responsive */}
<Navbar.Toggle aria-controls="navbarScroll" />
{/* Contenu du menu déroulant */}
<Navbar.Collapse id="navbarScroll">
<Nav
className="me-auto my-2 my-lg-0"
style={{ maxHeight: "100px" }}
navbarScroll
>
{/* Lien vers la page d'accueil */}
<Nav.Link href="/">Home</Nav.Link>
{/* Menu déroulant pour la gestion des clubs */}
<NavDropdown title="Clubs" id="navbarScrollingDropdown">
<NavDropdown.Item href="/clubs/add">
Créer un nouveau club
</NavDropdown.Item>
<NavDropdown.Item href="/clubs">
Liste des clubs
</NavDropdown.Item>
</NavDropdown>
{/* Menu déroulant pour la gestion des joueurs */}
<NavDropdown title="Joueurs" id="navbarScrollingDropdown">
<NavDropdown.Item href="/players/add">
Créer un nouveau joueur
</NavDropdown.Item>
<NavDropdown.Item href="/players">
Liste des joueurs
</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
);
};
// Exportation du composant Menu pour être utilisé dans d'autres fichiers
export default Menu;
Une fois le composant Menu.jsx codé, intégrez-le à la page Home pour le tester :
// Importation de React et du composant Component pour la création d'un composant de classe
import React, { Component } from 'react';
// Importation du composant Menu qui sera affiché sur la page d'accueil
import Menu from '../components/Menu';
// Définition du composant Home en tant que classe React
export class Home extends Component {
// La méthode render() permet d'afficher le contenu du composant
render() {
return (
<div>
{/* Affichage du menu de navigation */}
<Menu />
</div>
);
}
}
// Exportation du composant Home pour être utilisé dans d'autres fichiers
export default Home;
📌 Mise en place des formulaires
Ajoutez les fichiers AddClub.jsx
et AddPlayer.jsx
avec les champs nécessaires pour la création.
// Importation des bibliothèques React et des composants nécessaires de Bootstrap
import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import axios from "axios"; // Bibliothèque pour effectuer les requêtes HTTP
import { useNavigate } from "react-router-dom"; // Hook pour la navigation
import Menu from "../../components/Menu"; // Importation du menu de navigation
// Définition du composant AddClub
const AddClub = () => {
// Hook pour la navigation après la soumission du formulaire
const navigate = useNavigate();
// États pour stocker les valeurs des champs du formulaire
const [nameClub, setNameClub] = useState(""); // Nom du club
const [logoClub, setLogoClub] = useState(""); // Logo du club (fichier)
const [validationError, setValidationError] = useState({}); // Stocke les erreurs de validation
// Gestionnaire de changement pour le fichier du logo
const changeHandler = (event) => {
setLogoClub(event.target.files[0]); // Stocke le fichier sélectionné
};
// Fonction d'ajout d'un club via une requête HTTP POST
const addClub = async (e) => {
e.preventDefault(); // Empêche le rechargement de la page lors de la soumission du formulaire
// Création d'un objet FormData pour envoyer des fichiers avec la requête
const formData = new FormData();
formData.append("nameClub", nameClub);
formData.append("logoClub", logoClub);
// Envoi de la requête à l'API pour ajouter un club
await axios
.post(`http://127.0.0.1:8000/api/clubs`, formData)
.then(() => navigate('/clubs')) // Redirige vers la liste des clubs après succès
.catch(({ response }) => {
if (response.status === 422) { // Si l'API retourne une erreur de validation
setValidationError(response.data.errors); // Stocke les erreurs
}
});
};
return (
<div>
{/* Affichage du menu de navigation */}
<Menu />
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-sm-12 col-md-6">
<div className="card">
<div className="card-body">
<h4 className="card-title">Création d'un nouveau club</h4>
<hr />
<div className="form-wrapper">
{/* Affichage des erreurs de validation si elles existent */}
{Object.keys(validationError).length > 0 && (
<div className="row">
<div className="col-12">
<div className="alert alert-danger">
<ul className="mb-0">
{Object.entries(validationError).map(([key, value]) => (
<li key={key}>{value}</li>
))}
</ul>
</div>
</div>
</div>
)}
{/* Formulaire d'ajout de club */}
<Form onSubmit={addClub}>
<Row>
<Col>
<Form.Group controlId="Name">
<Form.Label>Nom du club</Form.Label>
<Form.Control
type="text"
value={nameClub}
onChange={(event) => setNameClub(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="Logo" className="mb-3">
<Form.Label>Logo</Form.Label>
<Form.Control type="file" onChange={changeHandler} />
</Form.Group>
</Col>
</Row>
{/* Bouton de soumission du formulaire */}
<Button
variant="primary"
className="mt-2"
size="lg"
block="block"
type="submit"
>
Créer
</Button>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
// Exportation du composant AddClub pour être utilisé ailleurs dans l'application
export default AddClub;
Puis réalisons le fichier d’ajout de joueur qui sera un peu plus compliqué que celui des clubs car le formulaire d’ajout comporte une clé étrangère à renseigner (club_id). Ce code sera à insérer dans le fichier AddPlayer.js :
// Importation des bibliothèques React et des composants Bootstrap pour le formulaire
import React, { useState, useEffect } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import axios from "axios"; // Importation d'Axios pour les requêtes HTTP
import { useNavigate } from "react-router-dom"; // Hook pour la navigation entre pages
import Menu from "../../components/Menu"; // Importation du menu de navigation
// Définition du composant AddPlayer pour l'ajout d'un joueur
const AddPlayer = () => {
const navigate = useNavigate(); // Hook pour rediriger après l'ajout
// États pour stocker les valeurs du formulaire
const [firstName, setFirstName] = useState(""); // Prénom du joueur
const [lastName, setLastName] = useState(""); // Nom du joueur
const [height, setHeight] = useState(""); // Taille du joueur
const [position, setPosition] = useState(""); // Position sur le terrain
const [club_id, setClubId] = useState(""); // ID du club sélectionné
const [photoPlayer, setPhotoPlayer] = useState(""); // Fichier photo du joueur
const [validationError, setValidationError] = useState({}); // Gestion des erreurs
const [clubs, setClubs] = useState([]); // Liste des clubs disponibles
// Gestionnaire de changement pour l'image (photo du joueur)
const changeHandler = (event) => {
setPhotoPlayer(event.target.files[0]); // Stocke le fichier sélectionné
};
// Gestionnaire de changement pour la sélection du club
const handleChange = (event) => {
setClubId(event.target.value); // Met à jour l'ID du club sélectionné
};
// useEffect pour charger la liste des clubs disponibles dès que le composant est monté
useEffect(() => {
getClubs();
}, []);
// Fonction pour récupérer la liste des clubs via une requête GET
const getClubs = async () => {
await axios.get('http://127.0.0.1:8000/api/clubs')
.then(res => {
setClubs(res.data); // Stocke la liste des clubs dans le state
});
};
// Fonction pour ajouter un joueur via une requête HTTP POST
const addPlayer = async (e) => {
e.preventDefault(); // Empêche le rechargement de la page lors de la soumission
// Création d'un objet FormData pour envoyer les données avec le fichier
const formData = new FormData();
formData.append("firstName", firstName);
formData.append("lastName", lastName);
formData.append("height", height);
formData.append("position", position);
formData.append("club_id", club_id);
formData.append("photoPlayer", photoPlayer);
console.log(club_id); // Affichage de l'ID du club sélectionné pour debug
// Envoi des données au serveur
await axios.post(`http://127.0.0.1:8000/api/players`, formData)
.then(() => navigate("/players")) // Redirection vers la liste des joueurs après succès
.catch(({ response }) => {
if (response.status !== 200) {
setValidationError(response.data); // Stocke les erreurs de validation
}
});
};
return (
<div>
{/* Affichage du menu de navigation */}
<Menu />
<div className="container container mt-5">
<div className="row justify-content-center">
<div className="col-12 col-sm-12 col-md-6">
<div className="card">
<div className="card-body">
<h4 className="card-title">Création d'un nouveau joueur</h4>
<hr />
<div className="form-wrapper">
{/* Affichage des erreurs de validation */}
{Object.keys(validationError).length > 0 && (
<div className="row">
<div className="col-12">
<div className="alert alert-danger">
<ul className="mb-0">
{Object.entries(validationError).map(
([key, value]) => (
<li key={key}>{value}</li>
)
)}
</ul>
</div>
</div>
</div>
)}
{/* Formulaire d'ajout de joueur */}
<Form onSubmit={addPlayer}>
<Row>
<Col>
<Form.Group controlId="firstName">
<Form.Label>Prénom</Form.Label>
<Form.Control
type="text"
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="lastName">
<Form.Label>Nom</Form.Label>
<Form.Control
type="text"
value={lastName}
onChange={(event) => setLastName(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="height">
<Form.Label>Taille</Form.Label>
<Form.Control
type="text"
value={height}
onChange={(event) => setHeight(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="position">
<Form.Label>Position</Form.Label>
<Form.Control
type="text"
value={position}
onChange={(event) => setPosition(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="club">
<Form.Label>Club</Form.Label>
<Form.Select aria-label="Sélection du club" onChange={handleChange}>
<option>Choisissez un club</option>
{clubs.map(club => (
<option key={club.id} value={club.id}>
{club.nameClub}
</option>
))}
</Form.Select>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="PhotoPlayer" className="mb-3">
<Form.Label>Photo du joueur</Form.Label>
<Form.Control type="file" onChange={changeHandler} />
</Form.Group>
</Col>
</Row>
{/* Bouton de soumission */}
<Button
variant="primary"
className="mt-2"
size="lg"
block="block"
type="submit"
>
Créer un nouveau joueur
</Button>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
// Exportation du composant AddPlayer pour être utilisé ailleurs
export default AddPlayer;
📌 Création des listes
Créez Clubs.jsx
et Players.jsx
pour afficher les données sous forme de tableau.
Dans le fichier Clubs.js :
// Importation des bibliothèques React et des composants Bootstrap pour l'affichage des données
import React, { useEffect, useState } from "react";
import Table from "react-bootstrap/Table";
import Button from "react-bootstrap/Button";
import Menu from "../../components/Menu"; // Importation du menu de navigation
import axios from "axios"; // Importation d'Axios pour les requêtes HTTP
// Définition du composant Clubs pour afficher la liste des clubs
const Clubs = () => {
// Définition d'un état pour stocker les clubs récupérés depuis l'API
const [clubs, setClubs] = useState([]);
// useEffect est exécuté au montage du composant pour récupérer les clubs
useEffect(() => {
displayClubs();
}, []); // Le tableau vide signifie que l'effet ne s'exécute qu'une seule fois
// Fonction pour récupérer la liste des clubs via une requête GET
const displayClubs = async () => {
await axios.get("http://127.0.0.1:8000/api/clubs").then((res) => {
setClubs(res.data); // Stocke les clubs récupérés dans l'état
});
};
// Fonction pour supprimer un club via une requête DELETE
const deleteClub = (id) => {
axios.delete(`http://127.0.0.1:8000/api/clubs/${id}`).then(displayClubs);
};
return (
<div>
{/* Affichage du menu de navigation */}
<Menu />
<div className="container mt-5">
{/* Affichage du tableau des clubs */}
<Table striped bordered hover>
<thead>
<tr>
<th>Nom du club</th>
<th>Logo</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{/* Boucle sur le tableau des clubs et affichage des données */}
{clubs.map((club) => (
<tr key={club.id}>
<td>{club.nameClub}</td>
<td>
<img
src={`http://127.0.0.1:8000/storage/uploads/${club.logoClub}`}
width="75px"
alt="Logo du club"
/>
</td>
<td>
{/* Bouton pour supprimer un club */}
<Button
variant="danger"
onClick={() => {
deleteClub(club.id);
}}
>
Supprimer
</Button>
</td>
</tr>
))}
</tbody>
</Table>
</div>
</div>
);
};
// Exportation du composant Clubs pour être utilisé ailleurs
export default Clubs;
Si après avoir copier le code vos images ne s’affichent pas entrez cette ligne de commande dans votre API :
#php artisan storage:link
📌 Création de l’update
L’update se construit de la même manière que le create sauf que nous devons récupérer les informations avant de les modifier.
Tout d’abord, on commence par ajouter le lien de modification dans le fichier Clubs.js au niveau de la colonne
« Actions » de votre tableau :
<Link to={`/clubs/edit/${club.id}`} className='btn btn-success me-2'>
Edit
</Link>
Pensez à importer le composant Link en haut de votre fichier :
import { Link } from 'react-router-dom';
Ensuite compléter votre fichier EditClub.js de cette manière :
// Importation des bibliothèques React et des composants Bootstrap pour le formulaire
import React, { useState, useEffect } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import axios from "axios"; // Bibliothèque pour effectuer les requêtes HTTP
import { useNavigate, useParams } from "react-router-dom"; // Hooks pour la navigation et la récupération des paramètres d'URL
import Menu from "../../components/Menu"; // Importation du menu de navigation
// Définition du composant EditClub pour modifier un club existant
const EditClub = () => {
// Récupération de l'ID du club depuis les paramètres de l'URL
const { club } = useParams();
// Hook pour rediriger après la modification
const navigate = useNavigate();
// États pour stocker les valeurs des champs du formulaire
const [nameClub, setNameClub] = useState(""); // Nom du club
const [logoClub, setLogoClub] = useState(null); // Logo du club (fichier)
const [validationError, setValidationError] = useState({}); // Gestion des erreurs de validation
// useEffect pour récupérer les informations du club dès le chargement du composant
useEffect(() => {
getClub();
}, []);
// Fonction pour récupérer les informations du club via une requête GET
const getClub = async () => {
await axios
.get(`http://127.0.0.1:8000/api/clubs/${club}`)
.then(res => {
setNameClub(res.data.nameClub); // Stocke le nom du club dans l'état
})
.catch(error => {
console.log(error); // Affichage des erreurs dans la console pour debug
});
};
// Gestionnaire de changement pour le fichier du logo
const changeHandler = (event) => {
setLogoClub(event.target.files[0]); // Stocke le fichier sélectionné
};
// Fonction pour mettre à jour le club via une requête PATCH
const updateClub = async (e) => {
e.preventDefault(); // Empêche le rechargement de la page lors de la soumission
// Création d'un objet FormData pour envoyer les données avec le fichier
const formData = new FormData();
formData.append('_method', 'PATCH'); // Méthode HTTP pour mettre à jour les données
formData.append("nameClub", nameClub);
// Vérifie si un logo a été sélectionné avant de l'envoyer
if (logoClub !== null) {
formData.append("logoClub", logoClub);
}
// Envoi de la requête PATCH pour mettre à jour les informations du club
await axios
.post(`http://127.0.0.1:8000/api/clubs/${club}`, formData)
.then(() => navigate("/clubs")) // Redirection vers la liste des clubs après modification
.catch(({ response }) => {
if (response.status === 422) {
setValidationError(response.data.errors); // Stocke les erreurs de validation
}
});
};
return (
<div>
{/* Affichage du menu de navigation */}
<Menu />
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-12 col-sm-12 col-md-6">
<div className="card">
<div className="card-body">
<h4 className="card-title">Modifier un club</h4>
<hr />
<div className="form-wrapper">
{/* Affichage des erreurs de validation si elles existent */}
{Object.keys(validationError).length > 0 && (
<div className="row">
<div className="col-12">
<div className="alert alert-danger">
<ul className="mb-0">
{Object.entries(validationError).map(
([key, value]) => (
<li key={key}>{value}</li>
)
)}
</ul>
</div>
</div>
</div>
)}
{/* Formulaire de modification d'un club */}
<Form onSubmit={updateClub}>
<Row>
<Col>
<Form.Group controlId="Name">
<Form.Label>Nom du club</Form.Label>
<Form.Control
type="text"
value={nameClub}
onChange={(event) => setNameClub(event.target.value)}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="Logo" className="mb-3">
<Form.Label>Logo</Form.Label>
<Form.Control type="file" onChange={changeHandler} />
</Form.Group>
</Col>
</Row>
{/* Bouton de soumission du formulaire */}
<Button
variant="primary"
className="mt-2"
size="lg"
block="block"
type="submit"
>
Modifier
</Button>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
// Exportation du composant EditClub pour être utilisé ailleurs dans l'application
export default EditClub;