Last active
July 29, 2024 20:33
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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