React: Building Modern User Interfaces
Exit

Lesson 1 of 13 · React basics

Why React: components, declarative rendering and a first project with Vite

40 min Node.js 22 (Debian, git, npm, python3)

Objective

Understand the problem React solves, how it updates the screen, and where it fits in the ecosystem (Vite, Next.js, React Router). Then build the structure of a Vite project by hand, so you know what each file contains.

Key points

  • React is a JavaScript library for building user interfaces from components: functions that return markup.
  • Rendering is declarative: you describe the screen for a given state; React updates the DOM when the state changes.
  • React works in two phases: render (it calls your components to compute the result), then commit (it applies only the differences to the DOM).
  • React "only" handles the user interface. For tooling, you use Vite (development server, build) or a framework such as Next.js or React Router.
  • A Vite project has four pieces: package.json, index.html, src/main.jsx (entry point) and src/App.jsx (first component).

The problem: updating the screen by hand

Take the Tifawin rug cooperative in Azilal, which sells online. In the "Web" course, you wrote this kind of code to show the number of items in the cart:

// "Imperative" JavaScript: you tell the browser WHAT TO CHANGE, step by step
let nombre = 0;
const badge = document.querySelector('#badge-panier');
const bouton = document.querySelector('#ajouter');

bouton.addEventListener('click', () => {
  nombre = nombre + 1;
  badge.textContent = nombre;                 // don't forget to update the badge…
  if (nombre > 0) badge.classList.remove('cache');  // …and its visibility…
  // …and the total, and the "Order" button, and the summary at the bottom of the page…
});

With three elements on the screen, everything is fine. With a catalog, filters, a cart and an order form, each event must remember every area to update. Anything you forget becomes a bug: the badge shows 3, the summary shows 2.

React's answer: describe, don't modify

With React, you write what the screen should be for a given value. When the value changes, React recomputes and updates the DOM for you:

import { useState } from 'react';

export default function BoutonPanier() {
  const [nombre, setNombre] = useState(0);   // the state: the component's "memory"

  return (
    <div>
      <button onClick={() => setNombre(nombre + 1)}>Ajouter au panier</button>
      {nombre > 0 && <span className="badge">{nombre}</span>}
    </div>
  );
}

There is no more querySelector or textContent: the user interface is a function of the state. You can sum it up with a formula: UI = f(state).

Components: reusable building blocks

A component is a JavaScript function whose name starts with a capital letter and that returns markup (JSX, covered in the next lesson). You assemble components like building blocks:

function CarteProduit({ nom, prix }) {
  return (
    <article>
      <h2>{nom}</h2>
      <p>{prix} DH</p>
    </article>
  );
}

export default function Catalogue() {
  return (
    <main>
      <CarteProduit nom="Tapis Azilal 2 x 3 m" prix={4200} />
      <CarteProduit nom="Coussin en cuir de Fès" prix={350} />
    </main>
  );
}

The whole application becomes a component tree: App contains Entete, Catalogue and Panier; Catalogue contains CarteProduit components, and so on.

Render and commit: what people call the "virtual DOM"

You often hear that React uses a "virtual DOM". In practice, an update happens in three steps:

  1. Trigger: at startup (initial render), or when a state changes (setNombre(...)).
  2. Render: React calls your components. They return a lightweight description of the UI (JavaScript objects, not real DOM nodes). React compares this description with the one from the previous render: this is reconciliation.
  3. Commit: React applies only the differences to the real DOM. If only the badge changed, only the badge text is modified.

Practical consequence: a text field where the user is typing is not "erased" by a new render, because React does not touch what has not changed.

setNombre(3)
   │
   ▼
Render : BoutonPanier() is called again → <span>3</span> instead of <span>2</span>
   │
   ▼
Commit : a single change in the DOM, the text of the <span>
   │
   ▼
The browser repaints the screen

Remember: "rendering" means calling your components, not necessarily touching the DOM.

The ecosystem: React does not come alone

React takes care of the UI. A real application also needs to transform JSX, bundle files, manage pages and sometimes render on the server. So you choose a tool around it:

Tool Role When to choose it
Vite very fast development server, production build "client-side" application (SPA): dashboard, back office, shop served as static files
Next.js full framework: pages, server rendering, API routes public site that needs good search engine ranking, server rendering, large project
React Router navigation between pages; can also act as a full framework SPA with several pages (lesson 2.5)

In this course, you use Vite: it is the simplest way to learn, and the result (HTML, CSS and JS files) can be deployed on any nginx server.

React can also be added to part of an existing site (for example, a booking widget in a PHP page): you then mount React in a single element of the page.

Creating a Vite project (with network access)

On your machine, with Node.js 20 or later (see the "Node.js" course):

npm create vite@latest boutique -- --template react   # generates the project
cd boutique
npm install                    # downloads react, react-dom, vite…
npm run dev                    # development server on http://localhost:5173

Each time you save a file, the page updates without reloading (hot reload).

What the project contains

boutique/
├── package.json        # dependencies and scripts
├── index.html          # the only HTML page: an empty div + the entry script
├── vite.config.js      # Vite configuration (React plugin)
├── public/             # files copied as they are (favicon, robots.txt)
└── src/
    ├── main.jsx        # entry point: mounts React in the div
    ├── App.jsx         # root component
    └── index.css

package.json

{
  "name": "boutique-artisanat",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^5.0.0",
    "vite": "^7.0.0"
  }
}
  • react contains components, JSX and hooks; react-dom knows how to display them in a browser.
  • vite and its plugin are only used for development and build: they are devDependencies.

index.html

<!doctype html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Coopérative Tifawin</title>
  </head>
  <body>
    <div id="root"></div>                                  <!-- React will display everything here -->
    <script type="module" src="/src/main.jsx"></script>    <!-- Vite transforms the JSX -->
  </body>
</html>

src/main.jsx

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);

createRoot takes control of div#root; render displays the App component in it. StrictMode enables extra checks in development only (for example, it calls some code twice to reveal errors).

src/App.jsx

export default function App() {
  return <h1>Coopérative Tifawin</h1>;
}

Setting up your workstation

  • Editor: VS Code (or equivalent) with ESLint and Prettier.
  • React Developer Tools extension in Chrome, Firefox or Edge: it adds the Components and Profiler tabs to the developer tools (lesson 3.2).
  • Node.js LTS and an up-to-date npm.

Common pitfalls

  • Lowercase component name: <carteProduit /> is treated as an unknown HTML tag. A component always starts with a capital letter.
  • Opening index.html directly in the browser (double-click): the JSX is not transformed, and the page stays blank. Use npm run dev.
  • Putting vite in dependencies: it works, but it is not clean. Build tools go in devDependencies.
  • Believing that React replaces JavaScript: everything you have learned (arrays, objects, map, filter, modules, promises) is used in every line of React.

Lab

The lab has no network access: you build by hand the exact skeleton that npm create vite would have produced, for the cooperative's shop:

  1. the package.json as ES modules;
  2. the dev, build and preview scripts;
  3. the React and Vite dependencies (declared, not installed);
  4. the index.html page;
  5. the main.jsx entry point and the App.jsx component.

Quiz

  1. What does "declarative rendering" mean?
  2. What does React do during the render phase, and during the commit?
  3. Why is vite a development dependency?
  4. What is the <div id="root"> element of index.html for?
  5. When should you prefer Next.js over a Vite application?
Answers
  1. You describe the expected UI for a given state; React computes and applies the DOM changes itself.
  2. Render: it calls the components and compares the result with the previous render. Commit: it applies only the differences to the DOM.
  3. It is only used to develop and build the files; the production site does not need it.
  4. It is the container in which createRoot(...).render(<App />) displays the whole application.
  5. For a public site that needs server-side rendering (search engine ranking, fast first display) or server routes; Vite suits client-side applications served as static files.

Reference sources: React documentation in French (fr.react.dev, CC BY 4.0 license). Original MasterLinux text.

Practical exercises

To do on Node.js 22 (Debian, git, npm, python3)

The online terminal is coming soon. Meanwhile, do these tasks on your own Linux machine, a virtual machine or WSL.

  1. In /root/boutique, create the package.json file (npm init -y), then name the project boutique-artisanat and declare it as ES modules ("type" field set to "module").
    Show a hintcd /root/boutique && npm init -y && npm pkg set name=boutique-artisanat type=module
    Show the solution
    cd /root/boutique && npm init -y >/dev/null 2>&1 && npm pkg set name=boutique-artisanat type=module

    Check : node -e "const p = require('/root/boutique/package.json'); process.exit(p.name === 'boutique-artisanat' && p.type === 'module' ? 0 : 1)"

  2. Add the three scripts of a Vite project: dev runs vite, build runs vite build and preview runs vite preview.
    Show a hintnpm pkg set scripts.dev=vite "scripts.build=vite build" "scripts.preview=vite preview"
    Show the solution
    cd /root/boutique && npm pkg set scripts.dev=vite "scripts.build=vite build" "scripts.preview=vite preview"

    Check : node -e "const s = require('/root/boutique/package.json').scripts || {}; process.exit(s.dev === 'vite' && s.build === 'vite build' && s.preview === 'vite preview' ? 0 : 1)"

  3. The lab has no network: declare react and react-dom in the dependencies (dependencies) without installing them, then vite and @vitejs/plugin-react in the development dependencies (devDependencies). Each version starts with ^ (for example ^19.1.0 for react).
    Show a hintnpm pkg set dependencies.react=^19.1.0 dependencies.react-dom=^19.1.0 devDependencies.vite=^7.0.0 "devDependencies.@vitejs/plugin-react=^5.0.0"
    Show the solution
    cd /root/boutique && npm pkg set dependencies.react=^19.1.0 dependencies.react-dom=^19.1.0 devDependencies.vite=^7.0.0 "devDependencies.@vitejs/plugin-react=^5.0.0"

    Check : node -e "const p = require('/root/boutique/package.json'); const d = p.dependencies || {}; const dd = p.devDependencies || {}; const v = (x) => /^\^\d/.test(x || ''); process.exit(v(d.react) && v(d['react-dom']) && v(dd.vite) && v(dd['@vitejs/plugin-react']) && !d.vite ? 0 : 1)"

  4. Write /root/boutique/index.html: an HTML page in French (lang="fr") that contains a <div id="root"></div> container and loads the /src/main.jsx module with <script type="module" src="/src/main.jsx"></script>.
    Show a hintcat > /root/boutique/index.html <<EOF ... EOF, with <html lang="fr">, <body>, the root div, then the script tag
    Show the solution
    printf '%s\n' '<!doctype html>' '<html lang="fr">' '  <head>' '    <meta charset="UTF-8" />' '    <title>Coopérative Tifawin</title>' '  </head>' '  <body>' '    <div id="root"></div>' '    <script type="module" src="/src/main.jsx"></script>' '  </body>' '</html>' > /root/boutique/index.html

    Check : f=/root/boutique/index.html; grep -q 'lang="fr"' $f && grep -q '<div id="root"></div>' $f && grep -q '<script type="module" src="/src/main.jsx"></script>' $f

  5. Create the entry point /root/boutique/src/main.jsx: it imports createRoot from "react-dom/client" and the App component from "./App.jsx", then renders <App /> in the element with id root. Also create /root/boutique/src/App.jsx, which default-exports an App function that returns an <h1> heading.
    Show a hintTwo files: main.jsx (import, createRoot(...).render(<App />)) and App.jsx (export default function App() { return <h1>…</h1>; })
    Show the solution
    mkdir -p /root/boutique/src && printf '%s\n' 'import { StrictMode } from "react";' 'import { createRoot } from "react-dom/client";' 'import App from "./App.jsx";' '' 'createRoot(document.getElementById("root")).render(' '  <StrictMode>' '    <App />' '  </StrictMode>' ');' > /root/boutique/src/main.jsx && printf '%s\n' 'export default function App() {' '  return <h1>Coopérative Tifawin</h1>;' '}' > /root/boutique/src/App.jsx

    Check : m=/root/boutique/src/main.jsx; a=/root/boutique/src/App.jsx; grep -Eq 'import \{ *createRoot *\} from .react-dom/client.' $m && grep -Eq 'import App from .\./App(\.jsx)?.' $m && grep -Eq 'createRoot\(document\.getElementById\(.root.\)\)' $m && grep -Eq '<App */>' $m && grep -Eq 'export default function App\(\)' $a && grep -q '<h1>' $a

Official references consulted: Démarrage rapide — https://fr.react.dev/learn ; Installation — https://fr.react.dev/learn/installation ; Ajouter React à un projet existant — https://fr.react.dev/learn/add-react-to-an-existing-project ; Rendu et Commit — https://fr.react.dev/learn/render-and-commit. Original MasterLinux text.