145 lines
4.0 KiB
TypeScript
145 lines
4.0 KiB
TypeScript
/**
|
|
* Performance-optimized invitation hooks.
|
|
* Uses useSyncExternalStore for fine-grained reactivity.
|
|
*/
|
|
|
|
import { useSyncExternalStore, useMemo, useCallback } from 'react';
|
|
import type { Invitation } from '../../services/invitation.js';
|
|
import type { XOInvitation } from '@xo-cash/types';
|
|
import { useAppContext } from './useAppContext.js';
|
|
|
|
/**
|
|
* Get all invitations reactively.
|
|
* Re-renders when invitations are added or removed.
|
|
*/
|
|
export function useInvitations(): Invitation[] {
|
|
const { appService } = useAppContext();
|
|
|
|
const subscribe = useCallback(
|
|
(callback: () => void) => {
|
|
if (!appService) {
|
|
return () => {};
|
|
}
|
|
|
|
// Subscribe to invitation list changes
|
|
const onAdded = () => callback();
|
|
const onRemoved = () => callback();
|
|
|
|
appService.on('invitation-added', onAdded);
|
|
appService.on('invitation-removed', onRemoved);
|
|
|
|
return () => {
|
|
appService.off('invitation-added', onAdded);
|
|
appService.off('invitation-removed', onRemoved);
|
|
};
|
|
},
|
|
[appService]
|
|
);
|
|
|
|
const getSnapshot = useCallback(() => {
|
|
return appService?.invitations ?? [];
|
|
}, [appService]);
|
|
|
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
}
|
|
|
|
/**
|
|
* Get a single invitation by ID with selective re-rendering.
|
|
* Only re-renders when the specific invitation is updated.
|
|
*/
|
|
export function useInvitation(invitationId: string | null): Invitation | null {
|
|
const { appService } = useAppContext();
|
|
|
|
const subscribe = useCallback(
|
|
(callback: () => void) => {
|
|
if (!appService || !invitationId) {
|
|
return () => {};
|
|
}
|
|
|
|
// Find the invitation instance
|
|
const invitation = appService.invitations.find(
|
|
(inv) => inv.data.invitationIdentifier === invitationId
|
|
);
|
|
|
|
if (!invitation) {
|
|
return () => {};
|
|
}
|
|
|
|
// Subscribe to this specific invitation's updates
|
|
const onUpdated = () => callback();
|
|
const onStatusChanged = () => callback();
|
|
|
|
invitation.on('invitation-updated', onUpdated);
|
|
invitation.on('invitation-status-changed', onStatusChanged);
|
|
|
|
// Also subscribe to list changes in case the invitation is removed
|
|
const onRemoved = () => callback();
|
|
appService.on('invitation-removed', onRemoved);
|
|
|
|
return () => {
|
|
invitation.off('invitation-updated', onUpdated);
|
|
invitation.off('invitation-status-changed', onStatusChanged);
|
|
appService.off('invitation-removed', onRemoved);
|
|
};
|
|
},
|
|
[appService, invitationId]
|
|
);
|
|
|
|
const getSnapshot = useCallback(() => {
|
|
if (!appService || !invitationId) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
appService.invitations.find(
|
|
(inv) => inv.data.invitationIdentifier === invitationId
|
|
) ?? null
|
|
);
|
|
}, [appService, invitationId]);
|
|
|
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
}
|
|
|
|
/**
|
|
* Get invitation data with memoization.
|
|
* Returns stable references to prevent unnecessary re-renders.
|
|
*/
|
|
export function useInvitationData(invitationId: string | null): XOInvitation | null {
|
|
const invitation = useInvitation(invitationId);
|
|
|
|
return useMemo(() => {
|
|
return invitation?.data ?? null;
|
|
}, [invitation?.data.invitationIdentifier, invitation?.data.commits?.length]);
|
|
}
|
|
|
|
/**
|
|
* Hook to create invitations.
|
|
* Returns a memoized function to create invitations.
|
|
*/
|
|
export function useCreateInvitation() {
|
|
const { appService } = useAppContext();
|
|
|
|
return useCallback(
|
|
async (invitation: XOInvitation | string): Promise<Invitation> => {
|
|
if (!appService) {
|
|
throw new Error('AppService not initialized');
|
|
}
|
|
|
|
return await appService.createInvitation(invitation);
|
|
},
|
|
[appService]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook to get all invitations with their IDs.
|
|
* Useful for lists where you only need IDs (prevents re-renders on data changes).
|
|
*/
|
|
export function useInvitationIds(): string[] {
|
|
const invitations = useInvitations();
|
|
|
|
return useMemo(() => {
|
|
return invitations.map((inv) => inv.data.invitationIdentifier);
|
|
}, [invitations]);
|
|
}
|