Skip to content

Instantly share code, notes, and snippets.

@csandman
Last active July 29, 2024 20:33
Show Gist options
  • Save csandman/8f538150e5e7bf135de4330b145ed0f4 to your computer and use it in GitHub Desktop.
Save csandman/8f538150e5e7bf135de4330b145ed0f4 to your computer and use it in GitHub Desktop.
A wrapper for the seats.io `SeatsioSeatingChart` component which prevents stale value references in the callback functions, and attaches the SeatingChart object to a forwarded ref
import { forwardRef, useCallback, useRef } from 'react';
import { SeatsioSeatingChart } from '@seatsio/seatsio-react';
import type {
BookableObject,
Category,
ChartRendererCallbacks,
ChartRendererConfigOptions,
EventManager,
HoldToken,
Region,
SeatingChart,
SelectableObject,
TicketTypeJson,
} from '@seatsio/seatsio-react';
/**
* There is no publicly exported `EmbeddableProps` type from the `@seatsio/seatsio-react` package,
* so we have to define our own type here if we want to forward all of the available props to the
* `SeatsioSeatingChart` component.
*/
interface SeatMapProps extends ChartRendererConfigOptions {
onRenderStarted?: (chart: SeatingChart | EventManager) => void;
region: Region;
}
const SeatMap = forwardRef<SeatingChart, SeatMapProps>(
(
{
onChartRendered,
onChartRenderingFailed,
onChartRerenderingStarted,
onObjectClicked,
onObjectSelected,
onObjectDeselected,
onObjectMouseOver,
onObjectMouseOut,
onObjectStatusChanged,
onSelectedObjectBooked,
onSessionInitialized,
onHoldSucceeded,
onHoldFailed,
onHoldTokenExpired,
onReleaseHoldSucceeded,
onReleaseHoldFailed,
onSelectionValid,
onSelectionInvalid,
onFullScreenOpened,
onFullScreenClosed,
onFilteredCategoriesChanged,
...props
},
ref,
) => {
// We need to use refs here because the onObjectedSelected and onObjectDeselected callbacks
// are only attached to the chart once, leading to stale closures.
const callbackRef = useRef<ChartRendererCallbacks>({});
callbackRef.current = {
onChartRendered,
onChartRenderingFailed,
onChartRerenderingStarted,
onObjectClicked,
onObjectSelected,
onObjectDeselected,
onObjectMouseOver,
onObjectMouseOut,
onObjectStatusChanged,
onSelectedObjectBooked,
onSessionInitialized,
onHoldSucceeded,
onHoldFailed,
onHoldTokenExpired,
onReleaseHoldSucceeded,
onReleaseHoldFailed,
onSelectionValid,
onSelectionInvalid,
onFullScreenOpened,
onFullScreenClosed,
onFilteredCategoriesChanged,
};
const handleChartRendered = useCallback(
(chart: SeatingChart) => {
callbackRef.current.onChartRendered?.(chart);
if (typeof ref === 'function') {
ref(chart);
} else if (ref) {
// eslint-disable-next-line no-param-reassign
ref.current = chart;
}
},
[ref],
);
const handleChartRenderingFailed = useCallback((chart: SeatingChart) => {
callbackRef.current.onChartRenderingFailed?.(chart);
}, []);
const handleChartRerenderingStarted = useCallback((chart: SeatingChart) => {
callbackRef.current.onChartRerenderingStarted?.(chart);
}, []);
const handleObjectClicked = useCallback((object: SelectableObject) => {
callbackRef.current.onObjectClicked?.(object);
}, []);
const handleObjectSelected = useCallback(
(object: SelectableObject, selectedTicketType: TicketTypeJson) => {
callbackRef.current.onObjectSelected?.(object, selectedTicketType);
},
[],
);
const handleObjectDeselected = useCallback(
(object: SelectableObject, selectedTicketType: TicketTypeJson) => {
callbackRef.current.onObjectDeselected?.(object, selectedTicketType);
},
[],
);
const handleObjectMouseOver = useCallback((object: SelectableObject) => {
callbackRef.current.onObjectMouseOver?.(object);
}, []);
const handleObjectMouseOut = useCallback((object: SelectableObject) => {
callbackRef.current.onObjectMouseOut?.(object);
}, []);
const handleObjectStatusChanged = useCallback((object: BookableObject) => {
callbackRef.current.onObjectStatusChanged?.(object);
}, []);
const handleSelectedObjectBooked = useCallback((object: BookableObject) => {
callbackRef.current.onSelectedObjectBooked?.(object);
}, []);
const handleSessionInitialized = useCallback((holdToken: HoldToken) => {
callbackRef.current.onSessionInitialized?.(holdToken);
}, []);
const handleHoldSucceeded = useCallback(
(objects: BookableObject[], ticketTypes: TicketTypeJson[]) => {
callbackRef.current.onHoldSucceeded?.(objects, ticketTypes);
},
[],
);
const handleHoldFailed = useCallback(
(objects: BookableObject[], ticketTypes: TicketTypeJson[]) => {
callbackRef.current.onHoldFailed?.(objects, ticketTypes);
},
[],
);
const handleHoldTokenExpired = useCallback(() => {
callbackRef.current.onHoldTokenExpired?.();
}, []);
const handleReleaseHoldSucceeded = useCallback(
(objects: BookableObject[], ticketTypes: TicketTypeJson[]) => {
callbackRef.current.onReleaseHoldSucceeded?.(objects, ticketTypes);
},
[],
);
const handleReleaseHoldFailed = useCallback(
(objects: BookableObject[], ticketTypes: TicketTypeJson[]) => {
callbackRef.current.onReleaseHoldFailed?.(objects, ticketTypes);
},
[],
);
const handleSelectionValid = useCallback(() => {
callbackRef.current.onSelectionValid?.();
}, []);
const handleSelectionInvalid = useCallback((violations: string[]) => {
callbackRef.current.onSelectionInvalid?.(violations);
}, []);
const handleFullScreenOpened = useCallback(() => {
callbackRef.current.onFullScreenOpened?.();
}, []);
const handleFullScreenClosed = useCallback(() => {
callbackRef.current.onFullScreenClosed?.();
}, []);
const handleFilteredCategoriesChanged = useCallback(
(categories: Category[]) => {
callbackRef.current.onFilteredCategoriesChanged?.(categories);
},
[],
);
return (
<SeatsioSeatingChart
{...props}
onChartRendered={handleChartRendered}
onChartRenderingFailed={handleChartRenderingFailed}
onChartRerenderingStarted={handleChartRerenderingStarted}
onObjectClicked={handleObjectClicked}
onObjectSelected={handleObjectSelected}
onObjectDeselected={handleObjectDeselected}
onObjectMouseOver={handleObjectMouseOver}
onObjectMouseOut={handleObjectMouseOut}
onObjectStatusChanged={handleObjectStatusChanged}
onSelectedObjectBooked={handleSelectedObjectBooked}
onSessionInitialized={handleSessionInitialized}
onHoldSucceeded={handleHoldSucceeded}
onHoldFailed={handleHoldFailed}
onHoldTokenExpired={handleHoldTokenExpired}
onReleaseHoldSucceeded={handleReleaseHoldSucceeded}
onReleaseHoldFailed={handleReleaseHoldFailed}
onSelectionValid={handleSelectionValid}
onSelectionInvalid={handleSelectionInvalid}
onFullScreenOpened={handleFullScreenOpened}
onFullScreenClosed={handleFullScreenClosed}
onFilteredCategoriesChanged={handleFilteredCategoriesChanged}
/>
);
},
);
SeatMap.displayName = 'SeatMap';
export default SeatMap;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment