Skip to content

5. Chapter 4 - Introduction to [React]

5.1. Sources

This chapter is based on the official documentation for [React]: react.dev.

5.2. From AngularJS 1.x to [React]

The original document (2014) used AngularJS and 1.x: a JavaScript framework (not yet TypeScript) organized around $scope, controllers, directives (ng-repeat, ng-if…), and a change detection mechanism based on the “digestion loop ” ($digest) and variable monitoring ($watch).

[React] (created by Meta, 2013) is not a complete framework like AngularJS, 1.x, or [Angular]: it is a library dedicated to a single task: building interfaces from components. On its own, it provides neither a HTTP client, nor a router, nor a dependency injection mechanism —which is why the [RdvMedecins] client in this chapter relies, for these purposes, on the browser’s native API (fetch(), see below) and on a mechanism specific to [React], the Context, rather than on dedicated modules such as [HttpClient] or the [Angular] router. This chapter presents these concepts one by one, with short examples independent of the case study—the next chapter will show them in action in the complete [RdvMedecins] client, in [React] 19.

5.3. The Concept of a [React] Application: the “Single Page Application”

A [React] application is, like a [Angular] application, a single-page application (SPA): the server delivers only a single page (HTML) to the browser, containing a single file (JavaScript) that embeds all of the application’s logic. Once this page is loaded, there is never another full page reload: every user interaction (changing views, submitting a form, etc.) is handled by the JavaScript code already present in the browser, which updates the display and communicates with the server in the background via asynchronous HTTP requests (most often using JSON). This was already the principle behind the original document using AngularJS and 1.x, as well as the [Angular] variant in this course—it has not changed: only the library that manages the display differs from one variant to another.

5.4. Components: Functions and [JSX]

As in [Angular], a component is the basic unit of a [React] application: it generates what is to be displayed on the screen based on its current state and the properties it receives. The difference lies in the form: a [Angular] component is a decorated class, associated with a separate (or inline) HTML template; a [React] component is simply a JavaScript (or TypeScript) function that returns [JSX] - a syntax that resembles HTML, but which is actually transformed by the compiler into ordinary JavaScript function calls.

Here is a very small component, independent of the case study, equivalent to [CompteurComponent] presented in the [Angular] variant of this course:

// Compteur.tsx

import { useState } from 'react';

export function Compteur() {
  const [valeur, setValeur] = useState(0);

  function incrementer(): void {
    setValeur((v) => v + 1);
  }

  return (
    <>
      <p>Valeur : {valeur}</p>
      <button onClick={incrementer}>+1</button>
    </>
  );
}

Let’s comment on this code:

  • line 3: import { useState } from 'react'; [useState] is a hook [React]: a special function (recognizable by its "use" prefix) that allows a component function to maintain state that persists from one render to the next—a role comparable to that of a signal on the [Angular] side, or the $scope/AngularJS controller;
  • line 5: export function Counter() { — a [React] component is a regular function, exported: there is neither a decorator nor a custom selector (HTML) to declare (unlike [@Component]({ selector: … }) on the [Angular] side)—it is used directly as a tag with its own name, <Compteur />, within the [JSX] of a parent component;
  • line 6: const [valeur, setValeur] = useState(0); [useState](0) creates a state value initialized to 0 and returns a pair: the current value (value) and a function to replace it (setValeur). Array unpacking ([valeur, setValeur]) is simply a naming convention—there’s nothing stopping you from naming these two elements differently;
  • lines 8–10: setValeur((v) => v + 1); — calling setValeur does not modify `value` in place (the state values [React] are immutable): it instructs [React] to redraw the component with the new value calculated from the old one, exactly as monSignal.update(v => …) does on the [Angular] side;
  • lines 13–16: <p>Value: {value}</p> — the [JSX] returned by the EST function in the template: the curly braces { } insert a JavaScript expression (here, the current value)—playing the same role as the double curly braces {{ value() }} in the [Angular] template, but without the call parentheses: `value` is the data itself, not a signal that needs to be interpreted when called.

The <> … </> tag (a “fragment”) groups multiple elements without adding a superfluous HTML node around them—a technical detail of [JSX], which requires that a component function return only a single root element.

5.5. The local state: [useState] and props

In AngularJS 1.x, [Angular] detected that a data value had changed using a systematic check loop ($digest), which was replaced in the [Angular] variant of this course by signals (signal(), computed()). [React] adopts a third approach, which appears simpler: a component fully re-executes its function (and thus recalculates its entire [JSX]) every time one of its [useState] values changes - [React] then compares the result to the previous rendering and modifies only what has actually changed in the browser’s actual DOM (a technique called reconciliation, or “virtual DOM”). Unlike signals, a [useState] state does not “know” on its own what depends on it: the re-rendering mechanism of the entire component handles this, which is simpler to reason about but—in theory—slightly more expensive in very large applications, a deliberate trade-off in [React].

Fonction
Rôle
useState(valeurInitiale)
creates a local state and returns the pair [valeur, setValeur]; setValeur redraws the component with the new value
setValeur(nouvelleValeur)
replaces the state value with a previously calculated value
setValeur(v => ...)
calculates the new value based on the old one (see increment() above)

useMemo(() => ..., [deps])
recalculates a derived value only when one of the elements of [deps] changes—the conceptual equivalent of computed() on the [Angular] side

Two mechanisms complement [useState] to enable a component to communicate with its parent—we’ll encounter them constantly in the [RdvMedecins] client (next chapter), exactly where input()/output() were located on the [Angular] side:

  • props: a [React] component receives its inputs as simple function parameters (a destructured object, { doctors, onRechercher })—the direct equivalent of input.required<Doctor[]>() on the [Angular] side, and, conceptually, a AngularJS 1.x directive attribute;
  • callback props: a function provided by the parent (onRechercher: (criteria) => void), passed as a prop and then called by the child at the right time—the direct equivalent of output<...>() (and its .emit(value) method) on the [Angular] side, and custom events ($emit) from AngularJS 1.x.

5.6. No modules: each file is already self-contained

An application named AngularJS 1.x was organized into modules (angular.module(...)). [Angular] first adopted this idea in a more rigid form (the NgModule), before proposing standalone components as the default mode—a simplification that the [Angular] variant of this course has already adopted. [React] never needed this concept: a .tsx file exports a component using the export keyword from the JavaScript language itself, and another file uses it with a simple import—the standard ECMAScript modules, without any additional layer specific to the interface library:

// Parent.tsx

import { Compteur } from './Compteur';

export function Parent() {
  return (
    <div>
      <Compteur />
    </div>
  );
}

This is the style used throughout the [RdvMedecins] client (next chapter): no *.module.ts files, no imports: decorators [...] to keep up to date—a further simplification compared to AngularJS, 1.x, [Angular], and NgModule, and even the standalone components of [Angular] (which, however, still list their dependencies in imports: [...] from the decorator [@Component]).

5.7. Sharing Logic: Custom Hooks and [Context]

As in [NestJS] (Chapter 2), the client [RdvMedecins] needs logic shared among multiple components: communicating with the server, identifying the logged-in user, and changing the language. [Angular] addresses this need with injectable services ([@Injectable]({ providedIn: 'root' }) + inject(...))—the direct successor to .service()/.factory() from AngularJS 1.x. [React] does not have a built-in dependency injection mechanism; modern usage combines two tools already discussed above:

  • a custom hook—a function whose name begins with `use`, which can itself call other hooks ([useState], [useContext]…)—encapsulates reusable logic, just like a service method on the [Angular] side;
  • the Context ([React.createContext]) shares a value (state + functions) with an entire subtree of components without having to manually pass it down, prop by prop, through each intermediate level—the role previously played by providedIn: 'root' on the [Angular] side: a single instance, available wherever it is needed.

A brief example, independent of the case study, equivalent in concept to [MonService] presented in the [Angular] variant of this course:

// mon-contexte.tsx
import { createContext, useContext, type ReactNode } from 'react';

interface MonContexteValue {
  saluer: () => string;
}

const MonContexte = createContext<MonContexteValue | null>(null);

export function MonProvider({ children }: { children: ReactNode }) {
  const value: MonContexteValue = { saluer: () => 'Bonjour' };
  return <MonContexte.Provider value={value}>{children}</MonContexte.Provider>;
}

export function useMonContexte(): MonContexteValue {
  const contexte = useContext(MonContexte);
  if (contexte === null) {
    throw new Error('useMonContexte() doit être appelé sous un <MonProvider>');
  }
  return contexte;
}

Let’s comment on this code:

  • line 8: const MonContexte = createContext<MonContexteValue | null>(null); — creates the Context itself; `null` is the default value, used only if there is no `<MonProvider>` above it in the tree—a programming error detected on line 17;
  • lines 10–13: <MonContexte.Provider value={value}>{children}</MonContexte.Provider> — the Provider component makes the value available to all its descendants ({children}); this is the one that is placed, just once, near the root of the application (see main.tsx of the client [RdvMedecins], next chapter)—the equivalent of the implicit “registration” performed by providedIn: 'root' on the [Angular] side;
  • lines 15–21: export function useMonContexte(): MonContexteValue { — the custom hook that provides access to the Context value; this is what the components call (const mon = useMonContexte();), exactly as they would have called inject(MonService) on the [Angular] side.

5.8. Communicating with the server: [fetch]

[HttpClient] ([Angular]) was the direct successor to the $http service from AngularJS 1.x used by the original document. Since [React] is merely an interface library, it does not provide any client HTTP: the client [RdvMedecins] directly uses [fetch], the browser’s standard API (available without any additional dependencies):

1
2
3
const reponseHttp = await fetch('http://localhost:8080/hello');
const reponse = (await reponseHttp.json()) as { message: string };
console.log(reponse.message);

An important difference from $http and [HttpClient]: [fetch] directly returns a Promise (the standard asynchronous mechanism of JavaScript), not an Observable—no RxJS library, no .subscribe({ next, error }), no .pipe(map(...)). It can be used with .then(...), or, for better readability, with async/await (as shown above, which suspends the function’s execution until the promise resolves, without blocking the rest of the application). A network error or an error response is then handled with a simple try/catch block, rather than with the error branch of a RxJS subscription.

The useRdvService() hook of the [RdvMedecins] client (next chapter) systematically encapsulates this [fetch] call followed by .json(), for a specific reason explained at that point (the [Reponse]<T> wrapper common to all server responses).

5.9. Conditional rendering and lists: pure [JSX]

AngularJS 1.x used structural directives (ng-if, ng-repeat) to display an element conditionally or repeat a template block for each element in a list. The [Angular] variant of this course uses the built-in syntax [@if]/[@for]. [React] does not have a separate template syntax to learn: the [JSX] returned by a EST component of the JavaScript, and flow control is expressed using ordinary JavaScript operators:

{utilisateurConnecte ? (
  <p>Bienvenue !</p>
) : (
  <p>Veuillez vous connecter.</p>
)}

<ul>
  {listeItems.map((item) => (
    <li key={item.id}>{item.nom}</li>
  ))}
</ul>

Let’s comment on this code:

  • lines 1–5: utilisateurConnecte ? (...) : (...) — the ternary operator JavaScript replaces [@if]/[@else] ([Angular]) and ng-if (AngularJS 1.x): the condition determines which of the two expressions [JSX] is returned. For a condition with no alternatives, the && operator is sufficient: {condition && <p>...</p>} displays nothing at all if the condition is false (this is the style used, for example, by the banner ADMIN/USER in the App component of the [RdvMedecins] client, discussed in the next chapter);
  • line 8: listeItems.map((item) => (...)) — .map() (a standard array method JavaScript) replaces [@for] ([Angular]) and ng-repeat (AngularJS 1.x): it transforms each element of the array into a [JSX] element;
  • line 9: key={item.id} (required in a [JSX] list) — tells [React] how to recognize an element that has already been displayed when it redraws the list; this plays exactly the same role as track item.id after [@for] on the [Angular] side.

5.10. Summary: mappings [Angular] -> [React]

[Angular] (previous version of this course)
[React] (2026)

Composants standalone ([@Component])
Functional Components
Signaux (signal(), input(), output(), computed())
[useState](), props, prop callbacks, [useMemo]()
effect()
[useEffect]()
Template séparé (.html) + décorateur (.ts)
[JSX] embedded in the component (.tsx)

[@if], [@for]
Ternary operator / &&, .map()

Services injectables ([@Injectable]({ providedIn: 'root' })) + inject()
Custom hooks + [Context]

[HttpClient] (Observables RxJS)
[fetch] (Native Promises)

[@angular/router] (table de routage vide)
No router (same conclusion, taken a step further)

Angular CLI / [@angular/build]
[Vite]

These concepts are sufficient to tackle the [RdvMedecins] client in the next chapter: each new use will nevertheless be re-explained there, as the code progresses, at the exact point where it appears.