React Blog Course

1 Création d'un blog avec React, TypeScript et Bootstrap

Contexte Général

Nous allons créer un mini-blog complet avec les fonctionnalités suivantes :

  • ? Affichage d'une liste d'articles
  • ✏️ Ajout de nouveaux articles via un formulaire
  • ? Mise en page moderne avec Bootstrap
  • ?️ Typage strict avec TypeScript

? Prérequis

  • Connaissances de base en JavaScript et React
  • Node.js installé sur votre machine
  • VS Code (ou autre éditeur moderne)

?️ Outils utilisés

  • Create React App avec template TypeScript
  • React Hooks (useState)
  • Bootstrap 5 pour le styling

2 Structure de base de l'application

Objectif :

Initialiser le composant principal App.tsx qui servira de point d'entrée à notre application. Nous commençons simplement avec un titre.

import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>Bienvenue sur mon Blog</h1> </div> ); } export default App;

Analyse du code :

Ligne Explication
import React from 'react'; Importe la bibliothèque React (nécessaire pour le JSX)
import './App.css'; Importe les styles CSS spécifiques à ce composant
function App() Déclaration du composant principal sous forme de fonction
className="App" Classe CSS pour styliser le conteneur principal (note: en JSX on utilise className au lieu de class)
export default App; Permet d'utiliser ce composant dans d'autres fichiers

3 Affichage des articles

Objectif :

Modifier App.tsx pour afficher une liste d'articles. Nous utilisons :

  • Un tableau d'objets pour stocker les données des articles
  • La méthode map() pour itérer sur les articles
  • Le système de key pour aider React à optimiser le rendu
function App() { const articles = [ { id: 1, title: "Premier article", content: "Ceci est le contenu du premier article." }, { id: 2, title: "Deuxième article", content: "Ceci est le contenu du deuxième article." }, ]; return ( <div className="App"> <h1>Bienvenue sur mon Blog</h1> <div className="articles-list"> {articles.map((article) => ( <div key={article.id} className="article"> <h2>{article.title}</h2> <p>{article.content}</p> </div> ))} </div> </div> ); }

Points clés :

Élément Explication
const articles = [...] Déclaration d'un tableau contenant nos articles (simule une base de données)
articles.map() Méthode JavaScript pour parcourir un tableau et retourner du JSX pour chaque élément
key={article.id} Clé unique obligatoire pour aider React à optimiser le rendu des listes
{article.title} Syntaxe JSX pour insérer dynamiquement des valeurs JavaScript

4 Création d'un composant Article

Objectif :

Extraire l'affichage d'un article dans un composant dédié pour :

  • Améliorer la réutilisabilité
  • Simplifier la maintenance
  • Isoler les responsabilités
import React from 'react'; // Définition du type des props interface ArticleProps { id: number; title: string; content: string; } // Composant Article avec typage TypeScript const Article: React.FC<ArticleProps> = ({ id, title, content }) => { return ( <div className="article"> <h2 className="article-title">{title}</h2> <p className="article-content">{content}</p> </div> ); }; export default Article;

Avantages de cette approche :

Élément Explication
interface ArticleProps Définit le contrat type pour les props du composant (typage TypeScript)
React.FC<ArticleProps> Type le composant comme Function Component avec des props spécifiques
Destructuring des props Permet d'accéder directement aux props sans utiliser props.title par exemple
Export default Permet d'importer le composant avec n'importe quel nom

5 Formulaire d'ajout d'article

Objectif :

Créer un composant formulaire qui permet :

  • De saisir un titre et un contenu
  • De valider pour ajouter un nouvel article
  • De communiquer avec le composant parent via une callback
import React, { useState } from 'react'; // Type des props attendues interface ArticleFormProps { onAddArticle: (title: string, content: string) => void; } const ArticleForm: React.FC<ArticleFormProps> = ({ onAddArticle }) => { // États pour gérer les champs du formulaire const [title, setTitle] = useState(''); const [content, setContent] = useState(''); // Gestion de la soumission const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (title.trim() && content.trim()) { onAddArticle(title, content); // Réinitialisation des champs setTitle(''); setContent(''); } }; return ( <form onSubmit={handleSubmit} className="article-form"> <div className="form-group"> <label htmlFor="title">Titre :</label> <input type="text" id="title" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Titre de l'article" required /> </div> <div className="form-group"> <label htmlFor="content">Contenu :</label> <textarea id="content" value={content} onChange={(e) => setContent(e.target.value)} placeholder="Contenu de l'article" required /> </div> <button type="submit" className="submit-btn"> Publier l'article </button> </form> ); }; export default ArticleForm;

Mécanismes importants :

Concept Explication
useState Hook React pour gérer l'état local du composant (ici les champs du formulaire)
Controlled components Les champs sont contrôlés par React (via value + onChange)
e.preventDefault() Empêche le rechargement de la page à la soumission
onAddArticle Callback pour communiquer avec le parent (remonte les données)
TypeScript typing Typage strict des props et des événements

6 Intégration de Bootstrap

Objectif :

Améliorer l'interface utilisateur sans effort avec Bootstrap :

  • Installation du package
  • Importation du CSS
  • Utilisation des classes utilitaires
# Installation de Bootstrap npm install bootstrap
// Dans index.tsx import 'bootstrap/dist/css/bootstrap.min.css';
// Exemple d'utilisation dans App.tsx <div className="container mt-5"> <h1 className="text-center mb-4">Mon Blog</h1> <div className="row justify-content-center"> <div className="col-md-8"> <ArticleForm onAddArticle={addArticle} /> <div className="mt-5"> {articles.map((article) => ( <div key={article.id} className="card mb-4"> <div className="card-body"> <h2 className="card-title">{article.title}</h2> <p className="card-text">{article.content}</p> </div> </div> ))} </div> </div> </div> </div>

Classes Bootstrap utilisées :

Classe Effet
container Conteneur responsive avec max-width
mt-5, mb-4 Marges top et bottom (1 à 5 scale)
text-center Alignement du texte au centre
card Composant carte avec ombre et bordure
card-body Espacement interne pour le contenu de la carte

Cours complet pour créer un blog avec React, TypeScript et Bootstrap

Étape 1 : Nettoyage de App.tsx

import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>Bienvenue sur mon Blog</h1> </div> ); } export default App;
Ligne Explication
import React from 'react'; Importe React, obligatoire pour interpréter le JSX
import './App.css'; Importe la feuille de style de ce composant
function App() { ... } Crée le composant principal de l'app
export default App; Exporte le composant pour qu'il soit utilisé dans index.tsx

Étape 2 : Afficher une liste statique d'articles

function App() { const articles = [ { id: 1, title: "Premier article", content: "Ceci est le contenu du premier article." }, { id: 2, title: "Deuxième article", content: "Ceci est le contenu du deuxième article." }, ]; return ( <div className="App"> <h1>Bienvenue sur mon Blog</h1> <div> {articles.map((article) => ( <div key={article.id}> <h2>{article.title}</h2> <p>{article.content}</p> </div> ))} </div> </div> ); }

Étape 3 : Composant Article réutilisable

import React from 'react'; interface ArticleProps { id: number; title: string; content: string; } const Article: React.FC<ArticleProps> = ({ id, title, content }) => { return ( <div> <h2>{title}</h2> <p>{content}</p> </div> ); }; export default Article;

Étape 4 : Formulaire pour ajouter un article

import React, { useState } from 'react'; interface ArticleFormProps { onAddArticle: (title: string, content: string) => void; } const ArticleForm: React.FC<ArticleFormProps> = ({ onAddArticle }) => { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (title && content) { onAddArticle(title, content); setTitle(''); setContent(''); } }; return ( <form onSubmit={handleSubmit}> <div> <label>Titre :</label> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Entrez le titre" /> </div> <div> <label>Contenu :</label> <textarea value={content} onChange={(e) => setContent(e.target.value)} placeholder="Entrez le contenu" /> </div> <button type="submit">Ajouter l'article</button> </form> ); }; export default ArticleForm;

Étape 5 : Ajout de Bootstrap

import 'bootstrap/dist/css/bootstrap.min.css'; // Dans App.tsx avec les classes Bootstrap <div className="App container mt-4"> <h1 className="text-center mb-4">Bienvenue sur mon Blog</h1> <ArticleForm onAddArticle={addArticle} /> <div className="mt-4"> {articles.map((article) => ( <div key={article.id} className="card mb-3"> <div className="card-body"> <h2 className="card-title">{article.title}</h2> <p className="card-text">{article.content}</p> </div> </div> ))} </div> </div>

Suite : CONTEXTE ACTUEL

 

Vous avez un projet React avec TypeScript dans frontendBlog, incluant :

Une liste d’articles affichée via le composant Article.

Un formulaire ArticleForm pour ajouter des articles.

Un style Bootstrap appliqué.

Nous allons ajouter :

Un bouton "Supprimer" dans Article avec une prop onDelete.

Un formulaire d’édition pour mettre à jour un article.

Une structure pour une future connexion au backend (simulée pour l’instant).

Étape 1 : Ajouter un bouton "Supprimer" dans Article

Objectif : Permettre de supprimer un article en cliquant sur un bouton.

Modifier Article.tsx :

Ouvre src/components/Article.tsx et mets à jour le code :

tsx

import React from 'react';

 

interface ArticleProps {

id: number;

title: string;

content: string;

onDelete: (id: number) => void; // Nouvelle prop pour supprimer

}

 

const Article: React.FC<ArticleProps> = ({ id, title, content, onDelete }) => {

return (

<div className="card mb-3">

<div className="card-body">

<h2 className="card-title">{title}</h2>

<p className="card-text">{content}</p>

<button

className="btn btn-danger"

onClick={() => onDelete(id)} // Appelle onDelete avec l'ID

>

Supprimer

</button>

</div>

</div>

);

};

 

export default Article;

Explication :

onDelete: (id: number) => void est une nouvelle prop qui prend un id et ne retourne rien (void).

Le bouton <button> utilise Bootstrap (btn btn-danger pour un style rouge) et appelle onDelete(id) quand on clique dessus avec une fonction fléchée pour passer l’id.

onClick={() => onDelete(id)} évite d’exécuter onDelete immédiatement (problème courant avec les événements).

Mettre à jour App.tsx pour gérer la suppression :

Ouvre src/App.tsx et modifie :

tsx

import React, { useState } from 'react';

import './App.css';

import Article from './components/Article';

import ArticleForm from './components/ArticleForm';

 

function App() {

const [articles, setArticles] = useState([

{ id: 1, title: "Premier article", content: "Ceci est le contenu du premier article." },

{ id: 2, title: "Deuxième article", content: "Ceci est le contenu du deuxième article." },

]);

 

const addArticle = (title: string, content: string) => {

const newArticle = {

id: Date.now(), // Utilise l'horodatage comme ID temporaire

title,

content,

};

setArticles([...articles, newArticle]);

};

 

const deleteArticle = (id: number) => {

setArticles(articles.filter((article) => article.id !== id)); // Supprime l'article avec l'ID donné

};

 

return (

<div className="App container mt-4">

<h1 className="text-center mb-4">Bienvenue sur mon Blog</h1>

<ArticleForm onAddArticle={addArticle} />

<div className="mt-4">

{articles.map((article) => (

<Article

key={article.id}

id={article.id}

title={article.title}

content={article.content}

onDelete={deleteArticle} // Passe la fonction de suppression

/>

))}

</div>

</div>

);

}

 

export default App;

Explication :

deleteArticle utilise filter pour créer une nouvelle liste sans l’article dont l’id correspond.

onDelete={deleteArticle} passe la fonction au composant Article.

setArticles met à jour l’état avec la nouvelle liste.

Vérifier :

Sauvegarde, lance npm start si nécessaire, et va sur http://localhost:3000.

Clique sur "Supprimer" pour un article : il devrait disparaître.

Étape 2 : Ajouter un formulaire d’édition pour mettre à jour un article

Objectif : Permettre de modifier un article existant.

Créer un composant EditArticleForm :

Dans src/components, crée EditArticleForm.tsx et ajoute :

tsx

import React, { useState } from 'react';

 

interface EditArticleFormProps {

article: { id: number; title: string; content: string };

onSave: (id: number, title: string, content: string) => void;

onCancel: () => void;

}

 

const EditArticleForm: React.FC<EditArticleFormProps> = ({ article, onSave, onCancel }) => {

const [title, setTitle] = useState(article.title);

const [content, setContent] = useState(article.content);

 

const handleSubmit = (e: React.FormEvent) => {

e.preventDefault();

if (title && content) {

onSave(article.id, title, content);

}

};

 

return (

<form onSubmit={handleSubmit}>

<div className="mb-3">

<label className="form-label">Titre :</label>

<input

type="text"

className="form-control"

value={title}

onChange={(e) => setTitle(e.target.value)}

/>

</div>

<div className="mb-3">

<label className="form-label">Contenu :</label>

<textarea

className="form-control"

value={content}

onChange={(e) => setContent(e.target.value)}

/>

</div>

<button type="submit" className="btn btn-primary me-2">

Sauvegarder

</button>

<button type="button" className="btn btn-secondary" onClick={onCancel}>

Annuler

</button>

</form>

);

};

 

export default EditArticleForm;

Explication :

article est une prop avec les données actuelles.

onSave et onCancel sont des fonctions passées par le parent.

Le formulaire utilise Bootstrap (form-control, mb-3) pour un style propre.

Mettre à jour Article.tsx pour inclure un bouton d’édition :

Modifie src/components/Article.tsx :

tsx

import React, { useState } from 'react';

import EditArticleForm from './EditArticleForm';

 

interface ArticleProps {

id: number;

title: string;

content: string;

onDelete: (id: number) => void;

onEdit: (id: number) => void; // Nouvelle prop pour lancer l'édition

}

 

const Article: React.FC<ArticleProps> = ({ id, title, content, onDelete, onEdit }) => {

const [isEditing, setIsEditing] = useState(false);

 

return (

<div className="card mb-3">

<div className="card-body">

{isEditing ? (

<EditArticleForm

article={{ id, title, content }}

onSave={(id, newTitle, newContent) => {

onEdit(id); // Simule la sauvegarde (à implémenter dans App)

setIsEditing(false);

}}

onCancel={() => setIsEditing(false)}

/>

) : (

<>

<h2 className="card-title">{title}</h2>

<p className="card-text">{content}</p>

<button className="btn btn-danger me-2" onClick={() => onDelete(id)}>

Supprimer

</button>

<button className="btn btn-warning" onClick={() => setIsEditing(true)}>

Modifier

</button>

</>

)}

</div>

</div>

);

};

 

export default Article;

Explication :

useState(false) gère l’état d’édition.

Si isEditing est true, affiche EditArticleForm; sinon, affiche les données et les boutons.

onEdit est une nouvelle prop (à définir dans App).

Mettre à jour App.tsx pour gérer l’édition :

Modifie src/App.tsx :

tsx

import React, { useState } from 'react';

import './App.css';

import Article from './components/Article';

import ArticleForm from './components/ArticleForm';

 

function App() {

const [articles, setArticles] = useState([

{ id: 1, title: "Premier article", content: "Ceci est le contenu du premier article." },

{ id: 2, title: "Deuxième article", content: "Ceci est le contenu du deuxième article." },

]);

 

const addArticle = (title: string, content: string) => {

const newArticle = {

id: Date.now(),

title,

content,

};

setArticles([...articles, newArticle]);

};

 

const deleteArticle = (id: number) => {

setArticles(articles.filter((article) => article.id !== id));

};

 

const editArticle = (id: number, newTitle: string, newContent: string) => {

setArticles(

articles.map((article) =>

article.id === id ? { ...article, title: newTitle, content: newContent } : article

)

);

};

 

return (

<div className="App container mt-4">

<h1 className="text-center mb-4">Bienvenue sur mon Blog</h1>

<ArticleForm onAddArticle={addArticle} />

<div className="mt-4">

{articles.map((article) => (

<Article

key={article.id}

id={article.id}

title={article.title}

content={article.content}

onDelete={deleteArticle}

onEdit={(id) => editArticle(id, articles.find((a) => a.id === id)!.title, articles.find((a) => a.id === id)!.content)} // À améliorer

/>

))}

</div>

</div>

);

}

 

export default App;

Explication :

editArticle met à jour l’article avec map, remplaçant l’article correspondant par une nouvelle version.

onEdit est passé avec une fonction qui appelle editArticle. (Note : La logique actuelle est basique ; nous l’améliorerons plus tard avec un état d’édition propre.)

L’appel à find est temporaire ; il faudra passer les nouvelles valeurs du formulaire.

Vérifier :

Sauvegarde et actualise.

Clique sur "Modifier", édite les champs, et clique sur "Sauvegarder" : l’article devrait être mis à jour.

Étape 3 : Préparer la connexion au backend (simulation)

Objectif : Mettre en place une structure pour une API REST future.

Simuler une API avec un état local :

Pour l’instant, articles est géré localement. Quand vous serez prêt pour Spring Boot, remplacez useState par des appels fetch ou axios.

Exemple futur avec fetch (à implémenter plus tard) :

Dans App.tsx, tu pourrais remplacer useState par :

tsx

const [articles, setArticles] = useState([]);

 

useEffect(() => {

fetch('http://localhost:8080/api/articles')

.then((response) => response.json())

.then((data) => setArticles(data));

}, []);

Explication :

useEffect appelle l’API au montage du composant.

fetch récupère les données du backend.

Cela nécessitera un backend fonctionnel (Spring Boot) et des ajustements (ex. gestion des erreurs).

Préparer addArticle pour l’API :Remplace par (futur) :

tsx

const addArticle = async (title: string, content: string) => {

const response = await fetch('http://localhost:8080/api/articles', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ title, content }),

});

const newArticle = await response.json();

setArticles([...articles, newArticle]);

};

Explication : Cela enverra une requête POST au backend.

Vérification globale

Lance npm start et teste :

Ajouter un article via le formulaire.

Supprimer un article avec le bouton "Supprimer".

Modifier un article avec "Modifier" et "Sauvegarder".

Si une erreur survient (ex. “Type error”), vérifie les imports et les types dans TypeScript.

Aucune note. Soyez le premier à attribuer une note !

Ajouter un commentaire

Anti-spam