❇️ Créer une application avec React.js

❇️ Créer une application avec React.js

React est un framework de Javascript développé initialement par Facebook en 2013. Il est aujourd’hui sous licence MIT, c’est-à-dire qu’il est modifiable par la communauté.

Ce framework ne gère que la vue de l’application MVC.

🔗 https://react.dev/

🖥 Prérequis

  • Node.js et npm installés
  • Projet Laravel 11 existant et configuré.
node -v
npm -v

🔗node -v v22.13.0
npm -v 10.9.2

🖥 Extensions pour l’IDE

 

Astuce : Visualisation structure du projet :

  • # brew install tree // Installation sous mac
  • # tree -a > project_structure.txt // generation globale
  • # find . -maxdepth 2 > project_structure.txt // Pour limiter la profondeur

🖥 Pour installer React

npx create-react-app 030225_front_office_tourisme
cd 030225_front_office_tourisme

Lien : 🔗https://create-react-app.dev/

🖥 Lancer le projet

npm start

❌ L’erreur indique que le module web-vitals est manquant ou n’est pas correctement installé dans votre projet React

npm uninstall web-vitals
  • ❌  Supprimer ou modifier reportWebVitals.js
  • ❌  Supprimer l’appel à reportWebVitals Dans src/index.js (ou src/main.js selon le  projet),
  • ❌  Supprimez src/reportWebVitals.js. Supprimez toutes les références à reportWebVitals dans le  projet.
// import reportWebVitals from './reportWebVitals';
// reportWebVitals();

installe react-router-dom qui va simplifier la gestion des routes et Sass qui va nous permettre de l’utiliser

npm i -s react-router-dom sass react-bootstrap bootstrap axios

🔗https://react-bootstrap.netlify.app/

🔗https://reactrouter.com/

🔗https://axios-http.com/fr/

Suppression des fichiers inutile

Garder les fichiers index.js et App.js

import 'bootstrap/dist/css/bootstrap.min.css';

🔗https://getbootstrap.com

🖥 Ajouter les fichiers

  • Pages
  • Dossiers “layouts – pages – compenents  – styles”

🖥 Mettre en place un système de routage

🖥 Créer les composants

  • Exemple :
  • Créer le premier composant “barre de navigation”.
  • Dans le dossier « components » – src “fichier Navbar.js”.
// Importation des modules nécessaires depuis React et React Router
import React from 'react';
import { NavLink } from "react-router-dom";

// Définition du composant Navbar
const Navbar = () => {
  return (
    // Création d'un conteneur div avec une classe CSS pour la navigation
    <div className='navigation'>
      <ul>
        {/* Lien de navigation vers la page d'accueil */}
        <NavLink to="/" className={(nav) => (nav.isActive ? "nav-active" : "")} >
          <li>Accueil</li>
        </NavLink>
        
        {/* Lien de navigation vers la page du blog */}
        <NavLink to="/blog" className={(nav) => (nav.isActive ? "nav-active" : "")} >
          <li>Mon blog</li>
        </NavLink>
      </ul>
    </div>
  );
};

// Exportation du composant pour pouvoir l'utiliser ailleurs dans l'application
export default Navbar;

🖥 Ajouter le composant aux pages

// Importation des modules nécessaires depuis React
import React from 'react';
import Navbar from '../components/Navbar';

// Définition du composant Home
const Home = () => {
  return (
    <div>
      {/* Affichage du composant Navbar pour la navigation */}
      <Navbar />
      
      {/* Titre principal de la page d'accueil */}
      <h1>Bienvenu sur la page d'accueil</h1>
    </div>
  );
};


// Exportation du composant pour pouvoir l'utiliser ailleurs dans l'application
export default Home;

🖥 Modifier et importer style.css dans index.jsx

dans le dossier styles créer un fichier style.scss

mkdir -p src/styles
touch src/styles/style.scss

Ajoutez ensuite un contenu de base pour éviter les erreurs

body { background-color: #f5f5f5; }

importer les styles dans les pages

import "./styles/style.scss";

🖥 Créer le composant logo et l’importer dans Navbar.js

// Importation du module React
import React from 'react';

// Définition du composant Logo
const Logo = () => {
  return (
    // Conteneur div avec une classe CSS pour styliser le logo
    <div className="logo">
      {/* Affichage de l'image du logo */}
      <img src="./logo.png" alt="logo" />
    </div>
  );
};

// Exportation du composant pour pouvoir l'utiliser ailleurs dans l'application
export default Logo;
// Importation des modules nécessaires depuis React et React Router
import React from 'react';
import { NavLink } from "react-router-dom";
import Logo from './Logo';

// Définition du composant Navbar
const Navbar = () => {
  return (
    // Conteneur div avec une classe CSS pour la navigation
    <div className='navigation'>
      {/* Affichage du composant Logo */}
      <Logo />
      
      {/* Liste de liens de navigation */}
      <ul>
        {/* Lien vers la page d'accueil */}
        <NavLink to="/" className={(nav) => (nav.isActive ? "nav-active" : "")} >
          <li>Accueil</li>
        </NavLink>
        
        {/* Lien vers la page du blog */}
        <NavLink to="/blog" className={(nav) => (nav.isActive ? "nav-active" : "")} >
          <li>Mon blog</li>
        </NavLink>
      </ul>
    </div>
  );
};

// Exportation du composant pour pouvoir l'utiliser ailleurs dans l'application
export default Navbar;

🖥 Installer Axios

  • Pour réaliser des requêtes GET, POST, PUT, PATCH ou DELETE pour interagir avec une API.

🖥 Exemple Utilisation Axios

🔗https://axios-http.com/fr/

// Importation des modules nécessaires depuis React et axios
import React, { useEffect, useState } from 'react';
import axios from "axios";

// Définition du composant PokemonList
const PokemonList = () => {
  // Déclare une variable d'état pour stocker la liste des Pokémon
  const [pokemons, setPokemons] = useState([]);


  // useEffect pour réaliser la requête HTTP et récupérer les données des Pokémon
  useEffect(() => {
    axios
      .get("https://pokeapi.co/api/v2/pokemon?limit=2000") // Requête API pour obtenir la liste des Pokémon
      .then((res) => setPokemons(res.data.results)); // Mise à jour de l'état avec les résultats récupérés
  }, []);


  // Affichage des Pokémon récupérés sous forme de liste
  return (
    <div>
      <ul>
        {pokemons.map((pokemon, index) => (
          <li key={index}> {pokemon.name} </li> // Affichage du nom de chaque Pokémon
        ))}
      </ul>
    </div>
  );
};

// Exportation du composant pour pouvoir l'utiliser ailleurs dans l'application
export default PokemonList;

 

🖥 Mise en place d’une connexion sécurisée côté frontend

🖥 React-hoock-form 🔗https://react-hook-form.com/

npm install react-hook-form

🖥 React-icons 🔗https://www.npmjs.com/package/react-icons

npm install react-icons --save

🖥 installer la bibliothèque

npm i jwt-decode

🖥 Créer un dossier services dans src.

🖥 Dans ce dossier créer un fichier Token.jsx.

// Importation du module pour décoder les JWT
import jwtDecode from "jwt-decode";

// Fonction pour récupérer le token depuis le stockage local
function getToken() {
  return localStorage.getItem('access_token');
}

// Fonction pour décoder le token s'il existe
let getDecodedToken = () => {
  if (getToken()) {
    return jwtDecode(localStorage.getItem('access_token'));
  } else {
    return false;
  }
};

// Fonction pour vérifier si le token est toujours valide
let getExpiryTime = () => {
  // Vérifie si le token est valide et qu'il n'a pas expiré
  if (getDecodedToken() && !(getDecodedToken().exp * 1000 < Date.now())) {
    return true;
  } else {
    return localStorage.removeItem('access_token');
  }
};

// Fonction pour récupérer les rôles stockés dans le token
let getRoles = () => {
  // Vérifie si le token est valide
  if (getExpiryTime()) {
    // Le champ "roles" est stocké en string JSON, on le parse puis le convertit en string
    return JSON.parse(getDecodedToken().roles).toString();
  } else {
    return false;
  }
};


// Fonction pour récupérer l'email stocké dans le token
let getEmail = () => {
  // Vérifie si le token est valide
  if (getExpiryTime()) {
    return getDecodedToken().email;
  } else {
    return false;
  }
};

// Fonction pour vérifier si l'utilisateur est connecté et a le rôle d'admin
let loggedAndAdmin = () => {
  // Vérifie si le token est valide et si l'utilisateur a le rôle d'administrateur
  return !!(getExpiryTime() && getRoles() === 'ROLE_ADMIN');
};

// Exportation des fonctions pour une utilisation dans d'autres fichiers
export default { getToken, getDecodedToken, getRoles, getEmail, loggedAndAdmin, getExpiryTime };

🖥 Créer un composant RegisterForm.jsx

dans un dossier auth à l’intérieur du dossier components.

// Importation des modules nécessaires
import React from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";
import { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useLocation, useNavigate } from "react-router-dom";
import axios from "axios";
import { AiOutlineEye, AiTwotoneEyeInvisible } from "react-icons/ai";

// Définition du composant RegisterForm
function RegisterForm() {
  // Modification du titre de la page
  document.title = "Inscription au site";

  let navigate = useNavigate();
  let location = useLocation();

  // Déclaration des méthodes et états du formulaire
  const {
    register,
    watch,
    control,
    handleSubmit,
    formState: { errors, isDirty, isValid },
  } = useForm({ mode: "onChange" });

  // États des champs du formulaire
  const email = watch("email", "");
  const password = watch("password", "");
  const name = watch("name", "");

  // États pour gérer l'affichage du mot de passe et les messages d'erreur
  const [showPassword, setShowPassword] = useState(false);
  const [toast, setShowToast] = useState(false);
  const [toastMessage, setToastMessage] = useState({});
  const [errMessage, setErrMessage] = useState("");

  // Fonction déclenchée lors de la soumission du formulaire
  const onSubmit = (data) => {
    registerForm();
  };

  // Fonction pour envoyer les données d'inscription à l'API
  const registerForm = async () => {
    setErrMessage("");
    try {
      const formData = new FormData();
      formData.append("email", email);
      formData.append("password", password);
      formData.append("name", name);

      const res = await axios.post(
        "http://127.0.0.1:8000/api/register/",
        formData,
        {
          headers: { "Content-Type": "multipart/form-data" },
        }
      );

      if (res.status === 200) {
        setErrMessage("");
        localStorage.setItem("access_token", res.data.token);
        navigate("/", { replace: true });
      } else {
        setToastMessage({
          message: "Une erreur est survenue",
          severity: "error",
        });
        setShowToast(true);
      }
    } catch (err) {
      console.log(err);
    }
  };


  // Fonction pour afficher ou masquer le mot de passe
  const handleClickShowPassword = () => {
    setShowPassword((prevShowPassword) => !prevShowPassword);
  };

  // Affichage du formulaire
  return (
    <Form onSubmit={handleSubmit(onSubmit)}>
      <h3 className="Auth-form-title">Créer un compte</h3>

      {/* Champ pour le pseudo */}
      <Form.Group className="mb-3" controlId="formBasicText">
        <Form.Label>Pseudo</Form.Label>
        <Form.Control
          type="text"
          placeholder="Votre pseudo"
          {...register("name", { required: "Pseudo obligatoire" })}
        />
        {errors.name && <Form.Text className="text-danger">{errors.name.message}</Form.Text>}
      </Form.Group>

      {/* Champ pour l'adresse mail */}
      <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Adresse mail</Form.Label>
        <Form.Control
          type="email"
          placeholder=""
          {...register("email", { required: "Adresse mail obligatoire" })}
        />
        {errors.email && <Form.Text className="text-danger">{errors.email.message}</Form.Text>}
      </Form.Group>

      {/* Champ pour le mot de passe */}
      <Form.Group className="mb-3" controlId="formBasicPassword">
        <Form.Label>Mot de passe</Form.Label>
        <InputGroup>
          <InputGroup.Text>
            <i onClick={handleClickShowPassword}>
              {showPassword ? <AiOutlineEye /> : <AiTwotoneEyeInvisible />}
            </i>
          </InputGroup.Text>
          <Form.Control
            type={showPassword ? "text" : "password"}
            placeholder="Mot de passe"
            {...register("password", {
              required: "Mot de passe est obligatoire",
              minLength: {
                value: 8,
                message: "Longueur minimale de 8 caractères",
              },
              pattern: {
                value: /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#:$%^&])/,
                message:
                  "Le mot de passe doit contenir une minuscule, une majuscule, un chiffre et un caractère spécial",
              },
            })}
          />
        </InputGroup>
        {errors.password && <Form.Text className="text-danger">{errors.password.message}</Form.Text>}
      </Form.Group>

      {/* Bouton de soumission */}
      <Button variant="primary" type="submit">Créer un compte</Button>
    </Form>
  );
}

// Exportation du composant pour une utilisation ailleurs
export default RegisterForm;

🖥 Construire une page Register.jsx dans le dossier page.

// Importation des composants nécessaires
import Players from "./pages/players/Players";
import AddPlayer from "./pages/players/AddPlayer";
import EditPlayer from "./pages/players/EditPlayer";
import Home from "./pages/Home";
import Register from "./pages/Register";

// Définition du composant principal de l'application
function App() {
  return (
    // Utilisation de BrowserRouter pour la gestion des routes
    <BrowserRouter>
      <Routes>
        {/* Route vers la page d'accueil */}
        <Route path="/" element={<Home />} />

        {/* Route vers la page d'inscription */}
        <Route path="/register" element={<Register />} />


        {/* Routes pour la gestion des clubs */}
        <Route path="/clubs" element={<Clubs />} />
        <Route path="/clubs/add" element={<AddClub />} />
        <Route path="/clubs/edit/:club" element={<EditClub />} />

        {/* Routes pour la gestion des joueurs */}
        <Route path="/players" element={<Players />} />
        <Route path="/players/add" element={<AddPlayer />} />
        <Route path="/players/edit/:player" element={<EditPlayer />} />

        {/* Route de secours pour toute URL inconnue, redirige vers la page d'accueil */}
        <Route path="*" element={<Home />} />
      </Routes>
    </BrowserRouter>
  );
}


// Exportation du composant principal
export default App;

🖥 Ajouter la route dans le App.jsx.

<Route path="/register" element={<Register />} />

🖥 Créer la page login

Composant LoginForm.jsx dans le même dossier que RegisterForm.jsx.

// Importation des modules nécessaires
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";
import { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { useLocation, useNavigate } from "react-router-dom";
import axios from "axios";
import { AiOutlineEye, AiTwotoneEyeInvisible } from "react-icons/ai";


// Définition du composant LoginForm
function LoginForm() {
  // Modification du titre de la page
  document.title = "Connexion au site";

  // Déclaration des états du formulaire
  const [errMessage, setErrMessage] = useState("");
  const [showPassword, setShowPassword] = useState(false);
  const [toast, setShowToast] = useState(false);
  const [toastMessage, setToastMessage] = useState({});

  // Utilisation de useForm pour gérer le formulaire
  const {
    register,
    control,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm({ defaultValues: { email: "", password: "" } });

  // Récupération des valeurs des champs
  const email = watch("email", "");
  const password = watch("password", "");

  // Gestion de la navigation et de la redirection après connexion
  let navigate = useNavigate();
  let location = useLocation();
  let from = location.pathname || "/";

  // Fonction pour gérer la connexion de l'utilisateur
  let login = async () => {
    try {
      let formData = new FormData();
      formData.append("email", email);
      formData.append("password", password);
      
      let res = await axios.post("http://127.0.0.1:8000/api/login/", formData, {
        headers: { "Content-Type": "multipart/form-data" },
      });
      
      if (res.status === 200) {
        localStorage.setItem("access_token", res.data.data.access_token.token);
        navigate("/home", { replace: true });
      }
    } catch (err) {}
  };

  // Fonction pour afficher ou masquer le mot de passe
  const handleClickShowPassword = () => {
    setShowPassword((prev) => !prev);
  };

  // Affichage du formulaire de connexion
  return (
    <Form onSubmit={handleSubmit(login)}>
      <h3 className="Auth-form-title">Connexion</h3>
      
      {/* Champ pour l'adresse mail */}
      <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Adresse mail</Form.Label>
        <Form.Control
          type="email"
          placeholder=""
          {...register("email", { required: "Mail obligatoire" })}
        />
        {errors.email && <Form.Text className="text-danger">{errors.email.message}</Form.Text>}
      </Form.Group>

      {/* Champ pour le mot de passe */}
      <Form.Group className="mb-3" controlId="formBasicPassword">
        <Form.Label>Mot de passe</Form.Label>
        <InputGroup>
          <InputGroup.Text>
            <i onClick={handleClickShowPassword}>
              {showPassword ? <AiOutlineEye /> : <AiTwotoneEyeInvisible />}
            </i>
          </InputGroup.Text>
          <Form.Control
            type={showPassword ? "text" : "password"}
            placeholder="Mot de passe"
            {...register("password", { required: "Mot de passe est obligatoire" })}
          />
        </InputGroup>
        {errors.password && <Form.Text className="text-danger">{errors.password.message}</Form.Text>}
      </Form.Group>

      {/* Bouton de soumission */}
      <Button variant="primary" type="submit">
        Se connecter
      </Button>

      {/* Lien vers la récupération de mot de passe */}
      <p className="forgot-password text-right mt-2">
        Mot de passe <a href="#">oublié?</a>
      </p>
    </Form>
  );
}

// Exportation du composant pour une utilisation ailleurs
export default LoginForm;

🖥 Créer la page Login.jsx.

// Importation des modules nécessaires
import Container from "react-bootstrap/Container";
import LoginForm from "../components/Auth/LoginForm";
import Menu from "../components/Menu";

// Définition du composant Login
function Login() {
  return (
    <div>
      {/* Affichage du menu de navigation */}
      <Menu />
      
      {/* Conteneur principal de la page de connexion */}
      <Container fluid className="loginContainer">
        {/* Affichage du formulaire de connexion */}
        <LoginForm />
      </Container>
    </div>
  );
}

// Exportation du composant pour une utilisation ailleurs
export default Login;

🖥 Ajouter la route dans App.jsx

Pour pouvoir envoyer un token dans la requête d’API, il faut le récupérer.

const token = localStorage.getItem("access_token");

 

// Fonction pour récupérer et afficher la liste des clubs
const displayClubs = async () => {
  await axios.get("http://127.0.0.1:8000/api/clubs", {
    headers: {
      "Content-Type": "multipart/form-data",
      Authorization: `Bearer ${token}`, // Utilisation du token pour l'authentification
    },
  }).then((res) => {
    setClubs(res.data); // Mise à jour de l'état avec les données récupérées
  });
};


// Fonction pour supprimer un club spécifique
const deleteClub = (id) => {
  axios.delete(`http://127.0.0.1:8000/api/clubs/${id}`, {
    headers: {
      "Content-Type": "multipart/form-data",
      Authorization: `Bearer ${token}`, // Authentification requise pour la suppression
    },
  }).then(displayClubs); // Rafraîchissement de la liste après suppression
};

🖥 Pour restreindre l’accès à certaines pages en utilisant le token.

il suffit de mettre en place une condition de ce type :

<Route path="/xxxxx" element={token ? <xxxxx/> : <Login/> }></Route>

 

📚 Documentation Officielle

  • React.js Official Documentation : La documentation officielle de React est la meilleure ressource pour comprendre les nouvelles fonctionnalités et API de React 19.
    🔗 https://react.dev

🎓 Tutoriels et Apprentissage

🎨 Templates et Modèles :

par