Conceptos básicosSe aplica aLite y Pro
Integración con React, refs y ciclo de vida
Renderiza SuperSchedulerComponent con las opciones del scheduler como props. Tras el montaje, accede al control mediante ref.current.control, una prop controlRef o useSchedulerControl(), que además te lo da como estado. Dale tamaño con height y heightSpec, mantén estables las props de objeto y de función, porque solo las props cuya identidad cambia llegan a control.update(), y confía en que el componente cree un control nuevo en cada montaje y lo libere al desmontarse, lo que lo hace seguro con Strict Mode.
SuperSchedulerComponent es un host de React muy fino alrededor de un control DOM, SuperScheduler.Scheduler. React renderiza un único <div> vacío; el control construye y actualiza todo lo que hay dentro, y el scroll, el zoom y el arrastre funcionan sin renders de React. Tu código React describe la configuración como props y habla con el control para las acciones imperativas, como hacer scroll hasta una fecha.
Esta página trata el componente de Pro. Lite sigue las mismas convenciones con menos opciones; las diferencias se enumeran al final.
Montar el componente
Cada opción del scheduler es una prop, y cada handler onXxx también:
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>
)
}Deberías ver una sección de 480 píxeles de alto con un mes de columnas de día y tres habitaciones. El componente en sí no acepta className, style ni id: colócalo mediante un elemento envolvente y da estilo al elemento raíz del control con cssClass o con las props classNames y styles, como se describe en Temas.
Las props exclusivas de React (controlRef, children, key, ref) se quedan en React. Todas las demás props se pasan al control, incluidos nombres que los tipos no declaran, así que el componente no avisa de una opción mal escrita: confía en TypeScript para detectarla.
Acceder al control
El control solo existe después de que el componente se monte. Hay tres formas de acceder a él:
| Método | Qué obtienes | Para qué usarlo |
|---|---|---|
ref en el componente | ref.current.control | Efectos y handlers de eventos en el mismo componente |
Prop controlRef | Un objeto ref cuyo current es el control, o un callback al que se llama con él al montar | Pasar el control a un componente padre o a código fuera de React |
useSchedulerControl() | { controlRef, control }: el ref y, además, el control como estado de React | Efectos que deben ejecutarse cuando aparece el control, como crear widgets |
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}
/>
</>
)
}En la práctica, algunos detalles importan:
- No leas nunca el control durante el render. En el primer render todavía no existe. Léelo en efectos, en handlers de eventos y en los callbacks del scheduler.
useSchedulerControl()cuesta un render adicional.controlesnullen el primer render y pasa a ser el control tras el montaje, así que los efectos que dependen de[control]se ejecutan en el momento adecuado. ElcontrolRefque devuelve es estable y se puede leer en los handlers sin esperar a ese render.- Un objeto
controlRefse limpia al desmontar (se pone anullsi todavía apunta a ese control). UncontrolRefde tipo callback se llama con el control al montar y no se llama connullal desmontar. - Los handlers reciben el control. Muchos argumentos de handler incluyen
args.control, y en todo handler escrito comofunctionnormal,thises el control.
Para volver a renderizar React cuando cambia el estado del scheduler (selección, zoom, área visible, historial), super-scheduler/hooks ofrece useScheduler({ track: [...] }), que devuelve { controlRef, control, state } y solo se actualiza con los temas que sigues, nunca una vez por fotograma de animación.
Dar tamaño al scheduler
El control ocupa todo el ancho de su contenedor. Su altura depende de dos opciones:
heightSpec | Comportamiento de height |
|---|---|
'Max' (por defecto) | El scheduler es tan alto como su contenido, hasta height píxeles (600 por defecto); a partir de ahí hace scroll vertical |
'Fixed' | Exactamente height píxeles, sea cual sea el número de filas |
'Auto' | Tan alto como su contenido, sin barra de scroll vertical propia |
'Parent100Pct' | Ocupa toda la altura del elemento padre |
height es la altura total, con las cabeceras de tiempo y la barra de scroll horizontal incluidas, así que no hace falta hacer cuentas con las cabeceras. height="100%" es un atajo para llenar el contenedor. En ese caso, el contenedor debe tener una altura definida:
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>
)
}control.setHeight(px) cambia la altura de forma imperativa y pasa a 'Fixed'. Dentro de SchedulerPanes, es el componente de paneles el que controla la altura; consulta Paneles y vistas guardadas.
Identidad de las props y memoización
En cada actualización de React, el componente compara cada prop con su valor anterior usando Object.is y solo envía a control.update() las que han cambiado. Las props sin cambios no cuestan nada. Las que cambian provocan un repintado síncrono de lo que afectan. Tres consecuencias:
- Los objetos y arrays en línea «cambian» en cada render.
timeHeaders={[{ groupBy: 'Day' }]}oresources={rows.map(...)}escritos en línea se vuelven a enviar cada vez que el padre se renderiza. - Las funciones en línea también cambian en cada render. Un
onBeforeEventRendernuevo invalida el render de todos los eventos; unonBeforeCellRendernuevo descarta la caché por celda. - Una prop que quitas vuelve al valor por defecto de la librería. Añadir y quitar una prop de forma condicional con un spread la alterna entre tu valor y el valor por defecto.
Mantén estables las props con constantes de módulo, useState, useMemo y useCallback. Un patrón práctico es un único objeto de configuración memorizado para opciones y handlers, con los datos pasados aparte:
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} />
</>
)
}Deberías ver cómo el tablero alterna entre densidad cómoda y compacta al pulsar el botón, mientras que los renders del padre que no tienen relación no envían nada al control.
Strict Mode, desmontaje y liberación
El componente crea un SuperScheduler.Scheduler nuevo en componentDidMount y llama a su dispose() en componentWillUnmount. En desarrollo, el Strict Mode de React monta, desmonta y vuelve a montar: obtienes un primer control que se libera de inmediato y un segundo que se queda. No hay fugas, pero tu propio código debe seguir la misma disciplina:
- Devuelve una función de limpieza en cada efecto que conecte algo al control (widgets de zoom, un minimapa, listeners, temporizadores). Un widget creado para el primer control, ya liberado, no sirve de nada y también hay que liberarlo.
- Protege los callbacks asíncronos. Una petición que se resuelve después de que el usuario haya salido de la página puede encontrarse un control liberado. Comprueba
control.disposed()antes de llamarlo: las llamadas a un control liberado pueden lanzar un error. - Tras el desmontaje,
ref.current.controles el control liberado ycontrol.disposed()devuelvetrue. Los refs creados concontrolRefyuseSchedulerControl()vuelven anull.
Si tu aplicación libera el control por su cuenta, el componente lo detecta y deja de enviarle actualizaciones.
Renderizado en servidor
Todos los puntos de entrada se pueden importar en Node sin DOM, así que el renderizado en servidor y el prerenderizado no fallan. La salida del servidor es solo el <div> host vacío: el control se crea en el navegador cuando el componente se monta. Reserva el espacio con un elemento envolvente con tamaño y, si el primer pintado importa, muestra un marcador de posición hasta el montaje. Consulta Renderizado en servidor y prerenderizado.
Sin React: el host imperativo
El mismo control funciona sobre cualquier elemento que sea tuyo, por ejemplo dentro de un componente de otro framework o en una página heredada:
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)acepta un elemento o su id.init()es obligatorio;update()antes deinit()lanzaSuperScheduler.Exception.update(options)aplica las opciones y repinta de forma síncrona.update()sin argumentos es un refresco completo que conserva la posición de scroll, pero borra la selección de rango de tiempo y el foco de teclado.- En este modo,
dispose()es responsabilidad tuya.
El punto de entrada del paquete también exporta el componente React, así que React sigue siendo una peer dependency instalada aunque solo uses el host imperativo.
Lite
super-scheduler-lite exporta un componente con el mismo nombre y las mismas convenciones de ref: ref.current.control y una prop controlRef. Diferencias: no hay useSchedulerControl; un controlRef de tipo callback se llama con null al desmontar; height es siempre una altura fija; y el control solo tiene update, scrollTo, scrollToResource, visibleStart, visibleEnd, disposed, dispose e init. Consulta el inicio rápido con Lite.
Siguientes pasos
- Mantén los eventos en el estado de React: Eventos controlados y callbacks.
- Pon componentes React dentro de eventos y cabeceras: Slots de renderizado React.
- Mide y ajusta conjuntos de datos grandes: Rendimiento y virtualización.