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;