React vscode

Introduction à React

Aujourd'hui, un nouveau sujet fait son arrivée sur ce blog, React !

React en 3 phrases

React est une bibliothèque JavaScript créée en 2013 par Facebook. Elle est libre (licence MIT depuis 2017).

Son but principal est de permettre aux développeurs de créer des applications Web monopage. C'est une bibliothèque que ne gère que l'interface de l'application. Elle peut être utilisée avec une autre bibliothèque ou un framework en complément, comme Django ou encore AngularJS. React est réputée pour ses bonnes performances.

Hello world like

Plutôt que de se contenter d'un simple Hello World, construisons plutôt un début d'application. Le problème posé est simple : il faut gérer une bibliothèque de livres.

Pour lancer notre application, par simplicité et par habitude, nous allons la lancer dans Docker. Les liens donnés en bas de l'article permettent d'installer React autrement si besoin.

Remarque : Les commandes données ici sont valables pour un système d'exploitation Linux.

Il faut se placer dans un répertoire dédié au projet puis lancer le container :

cd my-project-folder/
docker run --rm -it -v $(pwd):/app -p 3000:3000 node bash

Quelques explications rapides sur la commande. Le répertoire courant est monté dans /app. Le port 3000 est exposé pour avoir accès à l'application depuis la machine hôte une fois que celle-ci sera lancée.

Une fois dans le container, il faut donc se placer dans le dossier /app et lancer la commande qui permet de créer un nouveau projet React :

cd /app
npx create-react-app react-book-app

L'arborescence suivante a été créée :

Arborescence react book app

Trois dossiers sont présents. Le premier, node_modules, contient les dépendences du projet (le script d'installation précédent lance automatiquement la commande npm install). public contient les fichiers exposés par l'application. Un fichier html, une image favicon etc. Enfin le dernier dossier src est le plus intéressant. C'est ici que se trouvent les fichiers javascript. Dans le projet initial, un certain nombre de fichiers ont été créé dans ce dossier. Seul le fichier index.js nous sera utile dans notre petite application.

Avant de passer à la suite, testons l'application fraichement créée.

cd react-book-app
npm start

S'il n'y a pas d'erreur, le navigateur sur l'adresse http://localhost:3000 devrait afficher un résultat similaire à la capture suivante :

Helloworld React

C'est un bon début mais il faut continuer !

Bibliothèque de livres

Pour donner une belle apparence, fonts et icones, à notre application de gestion de livres, utilisons Bootstrap et Font Awesome.

Dans la section <head> du fichier public/index.html, il faut ajouter les 2 lignes suivantes :

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

React est une bibliothèque constituée de composants. Nous devons donc décomposer notre application en différents composants :

  • Book Dashboard C'est le composant principal, contenant les autres.
  • Book Listing Composant chargé de répertorier les livres. Les livres sont modifiables.
  • Toggleable Book Form Composant utilisé pour créer de nouveaux livres.

Une fois le travail terminé, l'application sera en mesure de traiter les 4 opérations CRUD (Create Read Update Delete).

C'est dans l'ordre précédent que nous allons créer les composants.

Voici notre fichier src/index.js avec le composant Book Dashboard :

import React from 'react';
import ReactDOM from 'react-dom';

class BookDashboard extends React.Component {
  state = {
    books: [
      {
        id: 1,
        title: 'A simple book',
        author: 'Jude Ben',
        description: `Lorem ipsum dolor sit amet, consectetur
                    adipiscing elit, sed do eiusmod tempor incididunt
                    ut labore et dolore magna aliqua. Ut enim ad minim
                    veniam, quis nostrud`
      },
      {
        id: 2,
        title: 'A book of secrets',
        author: 'James John',
        description: `Sed ut perspiciatis unde omnis iste natus
                    error sit voluptatem accusantium doloremque laudantium,
                    totam rem aperiam, eaque ipsa quae ab illo inventore
                    veritatis et quasi architecto beatae vitae dicta sunt
                    explicabo.`
      }
    ]
  }

  createNewBook = (book) => {
    book.id = Math.floor(Math.random() * 1000);
    this.setState({ books: this.state.books.concat([book]) });
  }

  updateBook = (newBook) => {
    const newBooks = this.state.books.map(book => {
      if (book.id === newBook.id) {
        return Object.assign({}, newBook)
      } else {
        return book;
      }
    });

    this.setState({ books: newBooks });
  }

  deleteBook = (bookId) => {
    this.setState({ books: this.state.books.filter(book => book.id !== bookId) })
  }

  render() {
    return (
      <main className="d-flex justify-content-center my-4">
        <div className="col-5">
          <BookList
            books={this.state.books}
            onDeleteClick={this.deleteBook}
            onUpdateClick={this.updateBook}
          />
          <ToggleableBookForm
            onBookCreate={this.createNewBook}
          />
        </div>
      </main>
    )
  }
}

Tout d'abord, les livres sont stockés dans une variable state. Des fonctions permettent de gérer la modification des livres et dans le rendu de la page, les livres sont passés en argument au composant BookList qui va nous intéresser ensuite. Enfin, la création d'un nouveau livre est gérée avec le ToggleableBookForm.

Ajoutons la classe BookList à la fin de notre fichier src/index.js.

class BookList extends React.Component {
  render() {
    const books = this.props.books.map(book => (
      <EditableBook
        key={book.id}
        id={book.id}
        title={book.title}
        author={book.author}
        description={book.description}
        onDeleteClick={this.props.onDeleteClick}
        onUpdateClick={this.props.onUpdateClick}
      ></EditableBook>
    ));
    return (
      <div>
        {books}
      </div>
    );
  }
}

class EditableBook extends React.Component {
  state = {
    inEditMode: false
  };
  enterEditMode = () => {
    this.setState({ inEditMode: true });
  }
  leaveEditMode = () => {
    this.setState({ inEditMode: false });
  }
  handleDelete = () => {
    this.props.onDeleteClick(this.props.id);
  }
  handleUpdate = (book) => {
    this.leaveEditMode()
    book.id = this.props.id;
    this.props.onUpdateClick(book);
  }
  render() {
    const component = () => {
      if (this.state.inEditMode) {
        return (
          <BookForm
            id={this.props.id}
            title={this.props.title}
            author={this.props.author}
            description={this.props.description}
            onCancelClick={this.leaveEditMode}
            onFormSubmit={this.handleUpdate}
          />
        );
      }
      return (
        <Book
          title={this.props.title}
          author={this.props.author}
          description={this.props.description}
          onEditClick={this.enterEditMode}
          onDeleteClick={this.handleDelete}
        />
      )
    }
    return (
      <div className="mb-3 p-4" style={{ boxShadow: '0 0 10px #ccc' }} >
        {component()}
      </div>
    )
  }
}

BookList reçoit et traite la liste de livres et la transforme en EditableBook. Cette dernière classe affiche soit un Book, soit un BookForm si l'utilisateur clique sur le bouton d'édition.

class Book extends React.Component {
  render() {
    return (
      <div className="card" /* style="width: 18rem;" */>
        <div className="card-header d-flex justify-content-between">
          <span>
            <strong>Title: </strong>{this.props.title}
          </span>
          <div>
            <span onClick={this.props.onEditClick} className="mr-2"><i className="fa fa-edit"></i></span>
            <span onClick={this.props.onDeleteClick}><i className="fa fa-trash"></i></span>
          </div>
        </div>
        <div className="card-body">
          {this.props.description}
        </div>
        <div className="card-footer">
          <strong>Author:</strong>  {this.props.author}
        </div>
      </div>
    );
  }
}

class BookForm extends React.Component {
  state = {
    title: this.props.title || '',
    author: this.props.author || '',
    description: this.props.description || ''
  }
  handleFormSubmit = (evt) => {
    evt.preventDefault();
    this.props.onFormSubmit({ ...this.state });
  }
  handleTitleUpdate = (evt) => {
    this.setState({ title: evt.target.value });
  }
  handleAuthorUpdate = (evt) => {
    this.setState({ author: evt.target.value });
  }
  handleDescriptionUpdate = (evt) => {
    this.setState({ description: evt.target.value });
  }
  render() {
    const buttonText = this.props.id ? 'Update Book' : 'Create Book';
    return (
      <form onSubmit={this.handleFormSubmit}>
        <div className="form-group">
          <label>
            Title
            </label>
          <input type="text" placeholder="Enter a title"
            value={this.state.title} onChange={this.handleTitleUpdate}
            className="form-control"
          />
        </div>

        <div className="form-group">
          <label>
            Author
            </label>
          <input type="text" placeholder="Author's name"
            value={this.state.author} onChange={this.handleAuthorUpdate}
            className="form-control"
          />
        </div>

        <div className="form-group">
          <label>
            Description
            </label>
          <textarea className="form-control" placeholder="Book Description"
            rows="5" value={this.state.description}
            onChange={this.handleDescriptionUpdate}
          >
            {this.state.description}
          </textarea>
        </div>
        <div className="form-group d-flex justify-content-between">
          <button type="submit" className="btn btn-md btn-primary">
            {buttonText}
          </button>
          <button type="button" className="btn btn-md btn-secondary" onClick={this.props.onCancelClick}>
            Cancel
            </button>
        </div>
      </form>
    )
  }
}

La classe Book ne sert qu'à faire de la mise en page. La seconde, BookForm, est plus intéressante. Elle créé un formulaire qui reprend les contenus des champs du livre en cas de mise à jour.

Il nous reste à gérer la création des livres avec le composant ToggleableBookForm.

class ToggleableBookForm extends React.Component {
  state = {
    inCreateMode: false
  }
  handleCreateClick = () => {
    this.setState({ inCreateMode: true });
  }
  leaveCreateMode = () => {
    this.setState({ inCreateMode: false });
  }
  handleCancleClick = () => {
    this.leaveCreateMode();
  }
  handleFormSubmit = (book) => {
    this.leaveCreateMode();
    this.props.onBookCreate(book);
  }
  render() {
    if (this.state.inCreateMode) {
      return (
        <div className="mb-3 p-4" style={{ boxShadow: '0 0 10px #ccc' }} >
          <BookForm
            onFormSubmit={this.handleFormSubmit}
            onCancelClick={this.handleCancleClick}></BookForm>
        </div>

      )
    }
    return (
      <button type="button" className="btn btn-md btn-secondary" onClick={this.handleCreateClick}>
        <i className="fa fa-plus"></i>
      </button>
    );
  }
}

Enfin, pour finaliser nos modifications dans le fichier src/index.js, il faut indiquer à l'application le composant à afficher :

ReactDOM.render(<BookDashboard />, document.getElementById('root'));

Nous sommes enfin prêts ! Il suffit de relancer l'application (si jamais vous l'aviez arrêté) et voilà le résultat :

React book app

C'est fini pour cette introduction à React. Nous verrons dans un prochain article comment faire persister les données ;-)

D'ici là, codez bien !

Sources

{{ message }}

{{ 'Comments are closed.' | trans }}