# Remote Data
URL: https://pro.rv-grid.com/guides/event-scheduler/remote-data/
Source: src/content/docs/guides/event-scheduler/remote-data.mdx
Description: Load scheduler resources and events from a server, refresh visible ranges, and handle optimistic save failures.
Use `remote` when events or resources are too large to ship up front, or when the scheduler must load only the visible date range.

For a comparison of host-owned full saves, host-owned incremental saves, and remote-backed saves, see [Data ownership and save flows](/guides/event-scheduler/event-editing/#data-ownership-and-save-flows).

```ts
grid.eventScheduler = {
  view: 'resourceTimeline',
  weekStartDate: '2026-06-08',
  remote: {
    enabled: true,
    loadEvents: async ({ dateRange, resourceIds }) => {
      const params = new URLSearchParams({
        start: dateRange.start,
        end: dateRange.end,
        resources: resourceIds?.join(',') ?? '',
      });
      const response = await fetch(`/api/scheduler/events?${params}`);
      return { events: await response.json() };
    },
    loadResources: async () => {
      const response = await fetch('/api/scheduler/resources');
      return { resources: await response.json() };
    },
    commitMutation: async (request) => {
      const response = await fetch('/api/scheduler/mutations', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(request),
      });
      if (!response.ok) {
        throw new Error('Save failed');
      }
      return { accepted: true, events: await response.json() };
    },
  },
};
```

The resource timeline demo includes remote-mode controls for refresh, load-more resources, and simulated save failure.

## Remote Options and State

Remote mode can load events, resources, availability, unassigned demand, and coverage for the visible range. It can also validate and commit accepted mutations.

```ts
const eventScheduler = {
  view: 'resourceTimeline',
  weekStartDate: '2026-06-08',
  dateRange: { start: '2026-06-08', end: '2026-06-14' },
  remote: {
    enabled: true,
    mode: 'remote',
    debounceMs: 150,
    overscanDays: 1,
    resourcePageSize: 50,
    loadEvents: ({ dateRange, filters, signal }) =>
      api.loadEvents({ dateRange, filters }, { signal }),
    loadResources: ({ cursor, limit, signal }) =>
      api.loadResources({ cursor, limit }, { signal }),
    loadAvailability: ({ dateRange, signal }) =>
      api.loadAvailability({ dateRange }, { signal }),
    loadCoverage: ({ dateRange, signal }) =>
      api.loadCoverage({ dateRange }, { signal }),
    validateMutation: ({ action, event, previousEvent }) =>
      api.validateScheduleChange({ action, event, previousEvent }),
    commitMutation: ({ action, event, previousEvent, events }) =>
      api.saveScheduleChange({ action, event, previousEvent, events }),
    optimistic: true,
    rollbackOnError: true,
    refreshPolicy: 'visible-range',
  },
};
```

Remote modes:

| Mode | Behavior |
| :-- | :-- |
| `local` | Remote controller is effectively off unless enabled hooks are used by the host. |
| `remote` | Visible data is loaded through remote hooks. |
| `hybrid` | Host can combine local side-channel data with remote range data. |

Remote state is available from `scheduler.getRemoteState()`. The plugin emits:

| Event | Purpose |
| :-- | :-- |
| `event-scheduler-remote-load-start` | A visible-range load begins. |
| `event-scheduler-remote-load-success` | A load target resolves. |
| `event-scheduler-remote-load-error` | A load target fails. |
| `event-scheduler-remote-mutation-pending` | A remote mutation starts. |
| `event-scheduler-remote-mutation-committed` | The backend accepts a mutation. |
| `event-scheduler-remote-mutation-rejected` | The backend rejects a mutation. |
| `event-scheduler-remote-state-change` | Loading, error, pending, paging, or empty state changes. |

Use `scheduler.refreshVisibleRange()` to reload the current range and `scheduler.loadMoreResources()` when paged resources report `hasMoreResources`.
