React vscode

React introduction

Today, a new topic makes its arrival on this blog, React!

React in a few words

React is a JavaScript library created in 2013 by Facebook. It is free (MIT license since 2017).

Its main goal is to allow developers to create single-page web applications. It is a library that only manages the interface of the application. It can be used with another library or a complementary framework, such as Django or AngularJS. React is known for its good performance.

Hello world like

Rather than being satisfied with a simple Hello World, let's build an application instead. The problem is simple: we need to manage a library of books.

To launch our application, for simplicity and habit, we will launch it in a Docker container. The links given at the bottom of the article allow to install React in another way if needed.

Note : The commands given here are valid for a Linux operating system.

You have to go to a directory dedicated to the project and then launch the container:

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

Some quick explanations about the previous command. The current directory is mounted in /app. Port 3000 is exposed to access the application from the host machine once it is running.

Once in the container, you have to go to the /app folder and launch the command to create a new React project:

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

The following tree structure has been created:

Arborescence react book app

Three folders are present. The first, node_modules, contains the project dependencies (the previous installation script automatically runs the npm install command). public contains the files exposed by the application. A html file, a favicon image etc. Finally the src directory is the most interesting. This is where the javascript files are located. In the initial project, a number of files were created in this folder. Only the index.js file will be useful in our little application.

Before moving on, let's test the newly created application.

cd react-book-app
npm start

If there are no errors, the browser at http://localhost:3000 should display a result similar to the following capture:

Helloworld React

It's a good start, but we have to keep going!

Book Library

To give a nice look, fonts and icons, to our book management application, use Bootstrap and Font Awesome.

In the <head> section of the public/index.html file, add the following 2 lines:

<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 is a library of components. We must therefore breakdown our application into different components :

  • Book Dashboard This is the main component, containing the others.
  • Book Listing Component in charge of listing the books. The books are modifiable.
  • Toggleable Book Form Component used to create new books.

Once the work is completed, the application will be able to process the 4 CRUD (Create Read Update Delete) operations.

We will create the components in the previous order.

Here is our src/index.js file with the Book Dashboard component :

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>
    )
  }
}

First, the books are stored in a state variable. Functions are used to manage the modification of the books and in the page rendering, the books are passed as arguments to the BookList component which will interest us next. Finally, the creation of a new book is managed with the ToggleableBookForm.

Let's add the BookList class at the end of our src/index.js file.

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 receives and processes the book list and turns it into an EditableBook. The latter class displays either a Book or a BookForm if the user clicks the edit button.

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>
    )
  }
}

The Book class is only used for layout. The second, BookForm, is more interesting. It creates a form that takes the contents of the book fields in case of an update.

We still have to manage the creation of books with the ToggleableBookForm component.

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>
    );
  }
}

Lastly, to finalize our modifications in the src/index.js file, we must tell the application which component to display:

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

We're finally ready! Just restart the application (if you had ever stopped it) and here's the result :

React book app

That's it for this introduction to React. We'll see in a next article how to make the data persistent ;-)

Until then, code well!

References

{{ message }}

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