# Intégration React, refs et cycle de vie

> Montez SuperSchedulerComponent, accédez au contrôle par les refs ou useSchedulerControl, dimensionnez-le, stabilisez les props et nettoyez en Strict Mode.

Source: https://superscheduler.org/fr/docs/react-integration/
Reviewed: 2026-10-07

Affichez SuperSchedulerComponent en passant les options du planificateur comme props. Après le montage, accédez au contrôle via ref.current.control, une prop controlRef ou useSchedulerControl(), qui vous le fournit aussi sous forme de state. Dimensionnez-le avec height et heightSpec, gardez stables les props objets et fonctions, car seules les props dont l’identité a changé atteignent control.update(), et laissez le composant créer un nouveau contrôle à chaque montage et le libérer au démontage, ce qui le rend sûr en Strict Mode.

`SuperSchedulerComponent` est un hôte React léger autour d’un contrôle DOM, `SuperScheduler.Scheduler`. React affiche une seule `<div>` vide ; le contrôle construit et met à jour tout ce qu’elle contient, et le défilement, le zoom et le glisser s’exécutent sans rendu React. Votre code React décrit la configuration sous forme de props et s’adresse au contrôle pour les actions impératives, comme faire défiler jusqu’à une date.

Cette page traite du composant Pro. Lite suit les mêmes conventions avec moins d’options ; les différences sont listées à la fin.

## Monter le composant
Chaque option du planificateur est une prop, et chaque handler `onXxx` aussi :

```tsx
// src/Planning.tsx
import { useMemo } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'

// Module constants: the same identity on every render, so they are applied once.
const ROOMS: SuperScheduler.ResourceData[] = [
  { id: 'r101', name: 'Room 101' },
  { id: 'r102', name: 'Room 102' },
  { id: 'r103', name: 'Room 103' },
]
const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
  { groupBy: 'Month' },
  { groupBy: 'Day', format: 'd' },
]

export function Planning({ bookings }: { bookings: SuperScheduler.EventData[] }) {
  // The control adopts the array it receives and edits it in place: give it its own copy.
  const owned = useMemo(() => bookings.slice(), [bookings])

  return (
    // The component renders a bare <div> with no className or style props: lay it out through
    // a wrapper, and style the control's root with cssClass (or classNames.root).
    <section className="planning" aria-label="Room planning">
      <SuperSchedulerComponent
        startDate="2026-10-01"
        days={31}
        scale="Day"
        cellWidth={44}
        timeHeaders={TIME_HEADERS}
        resources={ROOMS}
        events={owned}
        height={480}
        heightSpec="Fixed"
        cssClass="planning__scheduler"
      />
    </section>
  )
}
```
Vous devriez voir une section de 480 pixels de haut avec un mois de colonnes de jours et trois chambres. Le composant lui-même n’accepte ni `className`, ni `style`, ni `id` : gérez sa mise en page avec un élément enveloppe, et stylez l’élément racine du contrôle avec `cssClass` ou les props `classNames` et `styles`, comme décrit dans [Thèmes](https://superscheduler.org/fr/docs/theming/).

Les props propres à React (`controlRef`, `children`, `key`, `ref`) restent dans React. Toutes les autres props sont transmises au contrôle, y compris les noms que les typages ne déclarent pas : une option mal orthographiée n’est donc pas signalée par le composant. Comptez sur TypeScript pour la détecter.

## Accéder au contrôle
Le contrôle n’existe qu’après le montage du composant. Il y a trois façons d’y accéder :

| Méthode | Ce que vous obtenez | À utiliser pour |
|---|---|---|
| `ref` sur le composant | `ref.current.control` | Les effets et les handlers d’événements dans le même composant |
| Prop `controlRef` | Un objet ref dont `current` est le contrôle, ou un callback appelé avec lui au montage | Transmettre le contrôle à un parent, ou à du code hors de React |
| `useSchedulerControl()` | `{ controlRef, control }` : la ref, plus le contrôle sous forme de state React | Les effets qui doivent s’exécuter quand le contrôle apparaît, par exemple pour créer des widgets |

```tsx
// src/ControlAccess.tsx
import { useEffect, useRef } from 'react'
import { SuperScheduler, SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerEventMovedArgs } from 'super-scheduler'

const ROOMS: SuperScheduler.ResourceData[] = [{ id: 'r101', name: 'Room 101' }]

// 3. Inside handlers the control is `args.control` (and `this` in a non-arrow function).
function announceMove(args: SchedulerEventMovedArgs) {
  args.control.message(`Moved to ${args.newStart.toString('d MMM')}`)
}

// 1. A ref to the component: `ref.current.control` exists after mount.
export function WithComponentRef() {
  const ref = useRef<SuperSchedulerComponent>(null)
  useEffect(() => {
    ref.current?.control.scrollTo('2026-10-15', false, 'middle')
  }, [])
  return (
    <SuperSchedulerComponent
      ref={ref}
      startDate="2026-10-01"
      days={31}
      scale="Day"
      resources={ROOMS}
    />
  )
}

// 2. useSchedulerControl(): a stable ref for handlers, plus the control as state for effects.
export function WithHook() {
  const { controlRef, control } = useSchedulerControl()

  useEffect(() => {
    // `control` is null on the first render; the effect runs again once the scheduler mounts.
    control?.scrollTo(SuperScheduler.Date.today(), 'fast', 'middle')
  }, [control])

  const notify = () => controlRef.current?.message('Saved', 2000)

  return (
    <>
      <button type="button" onClick={notify}>
        Notify
      </button>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate="2026-10-01"
        days={31}
        scale="Day"
        resources={ROOMS}
        onEventMoved={announceMove}
      />
    </>
  )
}
```
Quelques détails comptent en pratique :

- **Ne lisez jamais le contrôle pendant le rendu.** Au premier rendu, il n’existe pas encore. Lisez-le dans les effets, les handlers d’événements et les callbacks du planificateur.
- **`useSchedulerControl()` coûte un rendu supplémentaire.** `control` vaut `null` au premier rendu et devient le contrôle après le montage : les effets qui dépendent de `[control]` s’exécutent donc au bon moment. La `controlRef` renvoyée est stable et peut être lue dans les handlers sans attendre ce rendu.
- **Un objet `controlRef` est vidé au démontage** (remis à `null` s’il pointe encore vers ce contrôle). Un callback `controlRef` est appelé avec le contrôle au montage, mais n’est pas appelé avec `null` au démontage.
- **Les handlers reçoivent le contrôle.** De nombreux arguments de handlers incluent `args.control`, et dans tout handler écrit comme une `function` classique, `this` est le contrôle.

Pour déclencher un nouveau rendu React quand l’état du planificateur change (sélection, zoom, zone visible, historique), `super-scheduler/hooks` fournit `useScheduler({ track: [...] })`, qui renvoie `{ controlRef, control, state }` et ne se met à jour que pour les sujets que vous suivez, jamais une fois par frame d’animation.

## Dimensionner le planificateur
Le contrôle occupe toute la largeur de son parent. Sa hauteur dépend de deux options :

| `heightSpec` | Comportement de `height` |
|---|---|
| `'Max'` (par défaut) | Le planificateur est aussi haut que son contenu, jusqu’à `height` pixels (600 par défaut) ; au-delà, il défile verticalement |
| `'Fixed'` | Exactement `height` pixels, quel que soit le nombre de lignes |
| `'Auto'` | Aussi haut que son contenu, sans barre de défilement verticale propre |
| `'Parent100Pct'` | Remplit la hauteur de l’élément parent |

`height` est la hauteur totale, en-têtes de temps et barre de défilement horizontale compris : aucun calcul sur les en-têtes n’est nécessaire. `height="100%"` est un raccourci pour remplir le parent. Le parent doit alors avoir une hauteur définie :

```tsx
// src/FullHeightPlanning.tsx
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'

interface FullHeightProps {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly bookings: SuperScheduler.EventData[]
}

export function FullHeightPlanning({ rooms, bookings }: FullHeightProps) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
      <header>Planning</header>
      {/* A definite height for the scheduler to fill; minHeight 0 lets the flex item shrink. */}
      <main style={{ flex: 1, minHeight: 0 }}>
        <SuperSchedulerComponent
          height="100%"
          startDate="2026-10-01"
          days={31}
          scale="Day"
          resources={rooms}
          events={bookings}
        />
      </main>
    </div>
  )
}
```
> **Behavior:**
> Avec la valeur par défaut `heightSpec: 'Max'`, un planificateur de deux lignes avec `height={320}` n’est pas plus haut que ces deux lignes. Si vous attendez une boîte de taille fixe, définissez `heightSpec="Fixed"` ou remplissez un parent dimensionné.

`control.setHeight(px)` modifie la hauteur de façon impérative et passe en `'Fixed'`. Dans `SchedulerPanes`, c’est le composant de volets qui gère la hauteur ; voir [Volets et vues enregistrées](https://superscheduler.org/fr/docs/panes-saved-views/).

## Identité des props et mémoïsation
À chaque mise à jour React, le composant compare chaque prop à sa valeur précédente avec `Object.is` et n’envoie à `control.update()` que celles qui ont changé. Les props inchangées ne coûtent rien. Les props modifiées déclenchent un repaint synchrone de ce qu’elles affectent. Trois conséquences :

- **Les objets et tableaux en ligne « changent » à chaque rendu.** `timeHeaders={[{ groupBy: 'Day' }]}` ou `resources={rows.map(...)}` écrits en ligne sont renvoyés chaque fois que le parent refait son rendu.
- **Les fonctions en ligne changent aussi à chaque rendu.** Un nouveau `onBeforeEventRender` invalide le rendu de tous les événements ; un nouveau `onBeforeCellRender` vide le cache par cellule.
- **Une prop que vous retirez revient à la valeur par défaut de la bibliothèque.** Ajouter et retirer une prop par un spread conditionnel la fait alterner entre votre valeur et la valeur par défaut.

Gardez les props stables avec des constantes de module, `useState`, `useMemo` et `useCallback`. Un modèle pratique consiste à regrouper options et handlers dans un seul objet de configuration mémoïsé, et à passer les données séparément :

```tsx
// src/Board.tsx
import { useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'

const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
  { groupBy: 'Month' },
  { groupBy: 'Day', format: 'd' },
]

interface BoardProps {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly bookings: SuperScheduler.EventData[]
  /** Should be stable (useCallback in the parent): it is a dependency of the config below. */
  readonly onOpen: (id: string) => void
}

export function Board({ rooms, bookings, onOpen }: BoardProps) {
  const [compact, setCompact] = useState(false)

  // Options and handlers in one memoized object: a parent re-render that changes none of the
  // dependencies sends nothing to the control.
  const config = useMemo<SchedulerProps>(
    () => ({
      startDate: '2026-10-01',
      days: 31,
      scale: 'Day',
      cellWidth: compact ? 28 : 44,
      density: compact ? 'compact' : 'comfortable',
      timeHeaders: TIME_HEADERS,
      onBeforeEventRender: (args) => {
        args.data.cssClass = compact ? 'booking booking--compact' : 'booking'
      },
      onEventClick: (args) => onOpen(String(args.e.id())),
    }),
    [compact, onOpen],
  )

  const owned = useMemo(() => bookings.slice(), [bookings])

  return (
    <>
      <button type="button" aria-pressed={compact} onClick={() => setCompact((value) => !value)}>
        Compact
      </button>
      <SuperSchedulerComponent {...config} resources={rooms} events={owned} />
    </>
  )
}
```
Le planning devrait alterner entre densité confortable et compacte quand vous appuyez sur le bouton, tandis que les rendus du parent sans rapport n’envoient rien au contrôle.

> **Tip:**
> Effectuez les actions impératives ponctuelles, comme la position de défilement initiale, dans un effet (`control.scrollTo(date)`), et non par des props. Une prop est réappliquée chaque fois qu’elle change ; un effet s’exécute quand vous le décidez.

## Strict Mode, démontage et libération
Le composant crée un **nouveau** `SuperScheduler.Scheduler` dans `componentDidMount` et appelle son `dispose()` dans `componentWillUnmount`. En développement, le Strict Mode de React monte, démonte puis remonte : vous obtenez un premier contrôle libéré aussitôt et un second qui reste. Rien ne fuit, mais votre propre code doit suivre la même discipline :

- **Renvoyez une fonction de nettoyage depuis chaque effet qui attache quelque chose** au contrôle (widgets de zoom, minimap, écouteurs, minuteurs). Un widget créé pour le premier contrôle, déjà libéré, est inutile et doit être libéré lui aussi.
- **Protégez les callbacks asynchrones.** Une requête qui aboutit après que l’utilisateur a quitté la page peut trouver un contrôle libéré. Vérifiez `control.disposed()` avant de l’appeler : les appels sur un contrôle libéré peuvent lever une erreur.
- **Après le démontage, `ref.current.control` est le contrôle libéré** et `control.disposed()` renvoie `true`. Les refs créées par `controlRef` et `useSchedulerControl()` sont remises à `null`.

Si votre application libère elle-même le contrôle, le composant s’en aperçoit et cesse de lui envoyer des mises à jour.

## Rendu serveur
Tous les points d’entrée peuvent être importés dans Node sans DOM : le rendu serveur et le prérendu ne plantent donc pas. La sortie serveur se limite à la `<div>` hôte vide : le contrôle est créé dans le navigateur au montage du composant. Réservez l’espace avec un élément enveloppe dimensionné et, si le premier affichage compte, montrez un espace réservé jusqu’au montage. Voir [SSR et prérendu](https://superscheduler.org/fr/docs/ssr-prerender/).

## Sans React : l’hôte impératif
Le même contrôle fonctionne sur n’importe quel élément qui vous appartient, par exemple dans un composant d’un autre framework ou dans une page existante :

```ts
// src/mount-planning.ts
import { SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'

/** Mounts a scheduler into an element you own and returns its teardown. */
export function mountPlanning(host: HTMLElement): () => void {
  const control = new SuperScheduler.Scheduler(host, {
    startDate: '2026-10-01',
    days: 31,
    scale: 'Day',
    resources: [
      { id: 'r101', name: 'Room 101' },
      { id: 'r102', name: 'Room 102' },
    ],
    events: [
      {
        id: 1,
        resource: 'r101',
        start: '2026-10-02T14:00:00',
        end: '2026-10-05T11:00:00',
        text: 'Booking 1042',
      },
    ],
    onEventMoved: (args) => console.info('moved', args.e.id(), args.newStart.value),
  })
  // Required: nothing is rendered before init(), and update() before init() throws.
  control.init()

  // Later changes go through update(), which repaints synchronously.
  control.update({ cellWidth: 56 })

  // dispose() releases the control's DOM and listeners when the host goes away.
  return () => control.dispose()
}
```
- `new SuperScheduler.Scheduler(elementOrId, options)` accepte un élément ou son id.
- `init()` est obligatoire ; `update()` avant `init()` lève une `SuperScheduler.Exception`.
- `update(options)` applique les options et redessine de façon synchrone. `update()` sans argument est un rafraîchissement complet qui conserve la position de défilement mais efface la sélection de plage de temps et le focus clavier.
- Dans ce mode, `dispose()` est sous votre responsabilité.

Le point d’entrée du paquet exporte aussi le composant React : React reste donc une dépendance pair installée, même si vous n’utilisez que l’hôte impératif.

## Lite
`super-scheduler-lite` exporte un composant du même nom avec les mêmes conventions de refs : `ref.current.control` et une prop `controlRef`. Différences : il n’y a pas de `useSchedulerControl` ; un callback `controlRef` est appelé avec `null` au démontage ; `height` est toujours une hauteur fixe ; et le contrôle ne propose que `update`, `scrollTo`, `scrollToResource`, `visibleStart`, `visibleEnd`, `disposed`, `dispose` et `init`. Voir [Démarrage rapide avec Lite](https://superscheduler.org/fr/docs/quick-start-lite/#imperative).

→ https://superscheduler.org/fr/examples/video-production/
## Étapes suivantes
- Gardez les événements dans le state React : [Événements contrôlés et callbacks](https://superscheduler.org/fr/docs/controlled-state/).
- Placez des composants React dans les événements et les en-têtes : [Slots de rendu React](https://superscheduler.org/fr/docs/react-render-slots/).
- Mesurez et optimisez les grands jeux de données : [Performances et virtualisation](https://superscheduler.org/fr/docs/performance-virtualization/).
