# Troubleshooting

> Fix the common integration problems: missing styles, zero-height hosts, null refs, empty views, wrong imports, duplicate React, Content Security Policy and Tailwind.

Source: https://superscheduler.org/en/docs/troubleshooting/
Reviewed: 2026-10-07

Most problems come from a few causes: the stylesheet is not imported, the host has no height for `height="100%"`, the control is read before mount, or the view shows one day of today while the data lives elsewhere (`days` defaults to 1 and `startDate` to today). Check also that date strings include seconds, that event `resource` values match resource ids exactly, and that only one copy of React is installed. Each section below gives the symptom, the cause and the fix.

Find the symptom, check the cause, apply the fix. Sections apply to Pro and Lite unless they name one edition. If your problem is not here, the [API reference](https://superscheduler.org/en/docs/api-reference/) lists every implemented option with its default, and its [reserved APIs](https://superscheduler.org/en/docs/api-reference/#reserved) section lists what is typed but not implemented.

## The scheduler renders without styles
**Symptom.** Rows and events appear, but without grid lines, colors or aligned headers.

**Cause.** The stylesheet is not loaded, or the wrong edition's stylesheet is.

**Fix.** Import it once, in your entry point or root layout: `import 'super-scheduler/styles.css'` for Pro, `import 'super-scheduler-lite/styles.css'` for Lite. The Pro stylesheet lives in `@layer super-scheduler` with zero-specificity selectors, so any unlayered rule of yours wins; a broad reset such as `* { border: 0 }` therefore removes the library's borders too (see [Tailwind](#tailwind)). `unstyled` turns the library's visual rules off on purpose.

## The scheduler is 0 px tall or not the height you set
**Symptom.** Nothing is visible, or the grid is shorter or taller than expected.

**Causes and fixes (Pro).**

- `heightSpec` defaults to `'Max'`: `height` (600 by default) is a ceiling, and the grid is as tall as its rows up to that value. Two rows with `height={320}` render about 130 px tall. Use `heightSpec="Fixed"` for a constant box.
- `height="100%"` fills the component's host element, an unstyled `<div>` that `SuperSchedulerComponent` renders inside your wrapper. If that `<div>` has no height, the scheduler collapses to 0 px. Give the wrapper a definite height and the host 100%:

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

interface Props {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly events: SuperScheduler.EventData[]
}

// height="100%" fills the component's own host <div>: .fill sizes it (see the CSS below).
export function FillParent({ rooms, events }: Props) {
  return (
    <div className="fill">
      <SuperSchedulerComponent
        height="100%"
        startDate="2026-10-01"
        days={31}
        scale="Day"
        resources={rooms}
        events={events}
      />
    </div>
  )
}
```
```css
.fill {
  height: 70vh; /* or a flex item with min-height: 0 */
}
.fill > div {
  height: 100%;
}
```

- `heightSpec="Auto"` sizes the control to its content with no vertical scrollbar, so the page scrolls instead.
- `SchedulerPanes` takes its own numeric `height` for all panes together.

In Lite, `height` is always a fixed number of pixels (400 by default). In both editions, when the wrapper is a flex item, give it `min-width: 0` in a row (or `min-height: 0` in a column); otherwise the flex item's automatic minimum size can let the grid push the layout wider or taller instead of scrolling.

## `ref.current` or `control` is null
**Cause.** The control is created in `componentDidMount`. During the first render, on the server and after unmount, there is no live control: `ref.current` is `null` before mount, and ref objects passed as `controlRef` are reset to `null` on unmount.

**Fix.** Read the control in effects and event handlers, never during render. `useSchedulerControl()` returns the control as state, so an effect can depend on it:

```tsx
// src/Planning.tsx
import { useEffect } from 'react'
import { SuperScheduler, SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'

const START = SuperScheduler.Date.today().addDays(-30)

export function Planning({ rooms }: { rooms: SuperScheduler.ResourceData[] }) {
  // `control` is null during the first render and the live control after mount.
  const { controlRef, control } = useSchedulerControl()

  useEffect(() => {
    if (control === null || control.disposed()) return
    control.scrollTo(SuperScheduler.Date.today(), false, 'middle')
  }, [control])

  return (
    <SuperSchedulerComponent
      controlRef={controlRef}
      startDate={START}
      days={90}
      scale="Day"
      resources={rooms}
    />
  )
}
```
In Pro, a function `controlRef` is called with the control on mount and is not called with `null` on unmount. A control reference kept from before an unmount points to a disposed control: check `control.disposed()` in asynchronous code. With the imperative API, call `init()` before anything else; `update()` before `init()` throws `SuperScheduler.Exception`.

## Nothing shows in the grid
Check these in order:

1. **Range.** `days` defaults to `1` and `startDate` to today. In Pro, `scale` also defaults to hourly cells (`'CellDuration'` with 60 minutes). Set `startDate`, `days` and `scale="Day"` to the period your data covers. Datasets from `super-scheduler/datasets` start on 2026-01-01 by default.
2. **Date strings.** `'2026-10-01T10:00'` throws `SchedulerDate: "2026-10-01T10:00" is not an ISO 8601 date`. Use `'2026-10-01'` or `'2026-10-01T10:00:00'`. Native `Date` objects do not type-check; convert them (see [civil dates](https://superscheduler.org/en/docs/locales-dates-timezones/#civil-dates)).
3. **Resource ids.** An event's `resource` must equal a resource `id` exactly: `1` and `'1'` are different. Events of unknown resources are not drawn.
4. **Trees (Pro).** `children` render only with `treeEnabled`, and a parent shows its children only when it has `expanded: true`.
5. **Filters and flags.** An active `control.events.filter()` or `control.rows.filter()`, or `hidden: true` on the event, hides it.
6. **Empty state.** With no visible rows, Pro shows `emptyState` if you set it; Lite shows "No resources" by default.

## Changes do not appear
- **Mutated in place.** The React component forwards a prop only when its identity changes. Pushing into the same `events` or `resources` array and re-rendering sends nothing. Pass a new array, or call `control.update()` after an in-place edit.
- **The array changes by itself (Pro).** The control adopts the `events` array you pass and splices it when events are added, removed or committed. Pass a copy (`useMemo(() => events.slice(), [events])`) if that array is shared state. A frozen array, as some state libraries produce in development, makes `control.events.add`, `update` and `remove` throw `TypeError`.
- **Updating an unknown id.** `control.events.update(data)` does nothing when the id is not loaded; use `add` for new events. `add` throws on a duplicate id.
- **`defaultEvents` changed.** It is read once at `init()`; later values are ignored with a development warning. Use controlled `events` for data that changes.
- **Cell hooks (Pro).** `onBeforeCellRender` results are cached per cell. If a cell depends on events, set `cellsAutoUpdated: true` on its resource or call `control.update()`.
- **A prop removed.** A prop that disappears between renders returns to its default value.

## Import errors and wrong subpaths
Only these entry points exist; anything else, such as `super-scheduler/dist/...`, fails with a "not exported" error from your bundler or Node:

- Pro: `super-scheduler`, `/styles.css`, `/react-render`, `/history`, `/minimap`, `/panes`, `/zoom-ui`, `/views`, `/ranges`, `/hooks`, `/tailwind`, `/datasets` and `/core`.
- Lite: `super-scheduler-lite` and `super-scheduler-lite/styles.css` only. Pro modules are not part of Lite.

TypeScript resolves these through the package `exports` with `moduleResolution` set to `bundler`, `node16` or `nodenext`; the older `node` setting works through the package's `typesVersions`. With `noUncheckedSideEffectImports` (TypeScript 5.6 and later), a stylesheet import needs a `declare module '*.css'` declaration, which bundler client types such as `vite/client` already provide. `super-scheduler/tailwind` is a CommonJS-style preset: load it with `require('super-scheduler/tailwind')`, or with a default import where your config supports CommonJS interop.

## Console messages
| Message | Meaning |
|---|---|
| `[super-scheduler] renderEvent needs the component from "super-scheduler/react-render"` | A React render prop (`renderEvent`, `renderCell`, `eventHover`, an `onBefore*DomAdd` handler...) was given to the root component. Import `SuperSchedulerComponent` from `super-scheduler/react-render`. |
| `super-scheduler: <feature> is not supported yet` | A reserved API: typed, accepted, inert. See [Reserved APIs](https://superscheduler.org/en/docs/api-reference/#reserved). |
| `[super-scheduler] events wins over defaultEvents` | Both props were given; `events` is used. |
| `[super-scheduler] defaultEvents is read only during init()` | A new `defaultEvents` value after mount is ignored. |
| `SuperScheduler Lite: unsupported option "..."` | Lite throws for any option it does not implement, also in production. `scale` must be `'Day'`, and resource `children`, `frozen`, `split` and `columns` require Pro. |

Pro prints these warnings only when `NODE_ENV` is not `production`, and reserved-API warnings only once per feature. Lite's errors are thrown in every build.

## "Invalid hook call" or two copies of React
**Symptom.** "Invalid hook call" from `useSchedulerControl` or `useScheduler`, React content in render slots that cannot see your context providers, or portal errors.

**Cause.** The library resolves a different copy of React than your app. Both editions declare React as a peer dependency and never bundle it, so this happens when the install or a link brings a second copy: a linked or locally built package, a monorepo with several React versions, or unmet peer ranges (18.2 or later, or 19).

**Fix.** `npm ls react react-dom` must show one version. Install the Pro tarball instead of linking a local copy. In Vite, add `resolve: { dedupe: ['react', 'react-dom'] }`; in webpack, alias `react` and `react-dom` to your app's copies.

## Content Security Policy errors
The library needs no inline scripts and no `'unsafe-inline'` styles: it loads as modules and writes geometry through `element.style`. If the console reports violations, check three things: lazily loaded chunks must be allowed by `script-src`; the Pro stylesheet's small `data:` SVG images need `img-src data:`; and inline `style` attributes inside HTML strings you pass (`html`, `bubbleHtml`) are blocked, so use classes. Details and a sample policy are in [Server rendering](https://superscheduler.org/en/docs/ssr-prerender/#csp).

## Tailwind removes borders or overrides the scheduler
Tailwind v3's preflight is unlayered and resets borders on every element, which beats the library's layered rules. Put preflight in a layer below SuperScheduler:

```css
@layer tw-base, super-scheduler;

@import 'super-scheduler/styles.css';

@layer tw-base {
  @tailwind base;
}
@tailwind components;
@tailwind utilities;
```

With Tailwind v4, declare the layer order before the imports so the library sits between `base` and your utilities:

```css
@layer theme, base, super-scheduler, components, utilities;

@import 'tailwindcss';
@import 'super-scheduler/styles.css';
```

See [Theming](https://superscheduler.org/en/docs/theming/) for the Tailwind preset and token mapping.

## Other surprises
- **Events snap to whole days (Pro).** `useEventBoxes` defaults to `'Always'`, which draws events over whole cells. Use `'Never'` to draw exact times, and add `eventMoveByCell` if dragging should stay anchored to cells.
- **A click leaves a selection behind (Pro).** A click on an empty cell is a one-cell selection reported to `onTimeRangeSelected` with `origin: 'click'`, and its shadow stays until the next selection or a click elsewhere. Call `args.control.clearSelection()` in the handler.
- **Keys do nothing (Pro).** `keyboardEnabled` defaults to `false`. `keyboardMode="Full"` needs it too. With several schedulers on a page, set `keyboardTarget="component"`.
- **Dates became objects (Pro).** After a drag or resize, the event's `start` and `end` are `SuperScheduler.Date` objects. `String(date)` and `JSON.stringify` give the civil ISO value; `date.toString('d MMM', locale)` formats it.
- **Wrong weekday.** `SuperScheduler.Date#getDay()` returns the day of the month. Use `getDayOfWeek()` (0 is Sunday) or `dayOfWeekISO()` (1 is Monday).

Related guides: [React integration](https://superscheduler.org/en/docs/react-integration/), [Controlled state](https://superscheduler.org/en/docs/controlled-state/), [Server rendering](https://superscheduler.org/en/docs/ssr-prerender/) and [Virtualization and performance](https://superscheduler.org/en/docs/performance-virtualization/).
