mirror of https://github.com/buster-so/buster.git
update to use new syntax
This commit is contained in:
parent
bdd1cbd64c
commit
f32bbf0a75
|
@ -31,12 +31,14 @@ export const useCollectionCreate = () => {
|
|||
{ route: '/collections/delete' },
|
||||
{ route: '/collections/delete:deleteCollections' },
|
||||
{
|
||||
preSetQueryDataFunction: {
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/collections/list:listCollections',
|
||||
callback: (data, variables) => {
|
||||
return data?.filter((collection) => !variables.ids.includes(collection.id)) || [];
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ export const useCollectionUpdate = () => {
|
|||
{ route: '/collections/update' },
|
||||
{ route: '/collections/update:collectionState' },
|
||||
{
|
||||
preSetQueryData(data, _variables) {
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/collections/get:collectionState',
|
||||
callback: (data, _variables) => {
|
||||
const variables = _variables as Partial<BusterCollection>;
|
||||
const newObject: BusterCollection = { ...data!, ...variables };
|
||||
return newObject;
|
||||
}
|
||||
},
|
||||
preSetQueryDataFunction: {
|
||||
{
|
||||
responseRoute: '/collections/list:listCollections',
|
||||
callback: (data, _variables) => {
|
||||
const existingData = data || [];
|
||||
|
@ -25,6 +29,7 @@ export const useCollectionUpdate = () => {
|
|||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ export const useDashboardUpdateConfig = ({
|
|||
{ route: '/dashboards/update' },
|
||||
{ route: '/dashboards/update:updateDashboard' },
|
||||
{
|
||||
preSetQueryDataFunction: {
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/dashboards/get:getDashboardState',
|
||||
callback: (data, variables) => {
|
||||
const newObject: BusterDashboardResponse = create(data!, (draft) => {
|
||||
|
@ -31,6 +32,7 @@ export const useDashboardUpdateConfig = ({
|
|||
return newObject;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -10,15 +10,26 @@ export const useFavoriteProvider = () => {
|
|||
const { mutate: addItemToFavorite } = useSocketQueryMutation(
|
||||
{ route: '/users/favorites/post' },
|
||||
{ route: '/users/favorites/post:createFavorite' },
|
||||
{ preSetQueryData: (prev, mutationParams) => [mutationParams, ...(prev || [])] }
|
||||
{
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/users/favorites/list:listFavorites',
|
||||
callback: (prev, mutationParams) => [mutationParams, ...(prev || [])]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: removeItemFromFavorite } = useSocketQueryMutation(
|
||||
{ route: '/users/favorites/delete' },
|
||||
{ route: '/users/favorites/post:createFavorite' },
|
||||
{
|
||||
preSetQueryData: (prev, mutationParams) =>
|
||||
prev?.filter((f) => f.id !== mutationParams.id) || []
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/users/favorites/list:listFavorites',
|
||||
callback: (prev, mutationParams) => prev?.filter((f) => f.id !== mutationParams.id) || []
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -27,13 +38,18 @@ export const useFavoriteProvider = () => {
|
|||
{ route: '/users/favorites/update:updateFavorite' },
|
||||
{
|
||||
awaitPrefetchQueryData: true,
|
||||
preSetQueryData: (prev, mutationParams) => {
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/users/favorites/list:listFavorites',
|
||||
callback: (prev, mutationParams) => {
|
||||
return mutationParams.favorites.map((id, index) => {
|
||||
let favorite = (prev || []).find((f) => f.id === id || f.collection_id === id)!;
|
||||
return { ...favorite, index };
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const bulkEditFavorites = useMemoizedFn(async (favorites: string[]) => {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { UseMutationOptions } from '@tanstack/react-query';
|
||||
import { BusterSocketResponseRoute } from '@/api/buster_socket';
|
||||
import type { InferBusterSocketResponseData, BusterSocketRequestRoute } from './types';
|
||||
|
||||
export type QueryDataStrategy = 'replace' | 'append' | 'prepend' | 'merge' | 'ignore';
|
||||
|
||||
export type PreSetQueryDataItem<TVariables> = {
|
||||
[Route in BusterSocketResponseRoute]: {
|
||||
responseRoute: Route;
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<Route>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<Route>;
|
||||
};
|
||||
}[BusterSocketResponseRoute];
|
||||
|
||||
export type SinglePreSetQueryDataItem<TRoute extends BusterSocketResponseRoute, TVariables> = {
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<TRoute>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<TRoute>;
|
||||
};
|
||||
|
||||
export type SocketQueryMutationOptions<
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError,
|
||||
TVariables
|
||||
> = Omit<
|
||||
UseMutationOptions<InferBusterSocketResponseData<TRoute>, TError, TVariables>,
|
||||
'mutationFn'
|
||||
> & {
|
||||
/**
|
||||
* Configuration for optimistically updating query data before the mutation completes.
|
||||
* Can be either a single item or an array of items.
|
||||
*/
|
||||
preSetQueryData?:
|
||||
| Array<PreSetQueryDataItem<TVariables>>
|
||||
| SinglePreSetQueryDataItem<TRoute, TVariables>;
|
||||
|
||||
/**
|
||||
* When true, adds a small delay before applying preSetQueryData to ensure React Query's cache
|
||||
* is properly initialized.
|
||||
* @default false
|
||||
*/
|
||||
awaitPrefetchQueryData?: boolean;
|
||||
|
||||
/**
|
||||
* Strategy for integrating mutation response data into existing query data.
|
||||
* @property 'replace' - Replace existing data
|
||||
* @property 'append' - Add to end of array
|
||||
* @property 'prepend' - Add to start of array
|
||||
* @property 'merge' - Merge objects (requires ID field)
|
||||
* @property 'ignore' - No automatic update
|
||||
* @default 'ignore'
|
||||
*/
|
||||
queryDataStrategy?: QueryDataStrategy;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { QueryDataStrategy } from './mutationTypes';
|
||||
import { BusterSocketResponseRoute } from '@/api/buster_socket';
|
||||
import { InferBusterSocketResponseData } from './types';
|
||||
|
||||
export const executeQueryDataStrategy = async <TRoute extends BusterSocketResponseRoute>(
|
||||
queryClient: QueryClient,
|
||||
queryKey: unknown[],
|
||||
data: InferBusterSocketResponseData<TRoute>,
|
||||
strategy: QueryDataStrategy
|
||||
) => {
|
||||
if (strategy === 'ignore') return;
|
||||
|
||||
const strategies: Record<Exclude<QueryDataStrategy, 'ignore'>, () => Promise<void>> = {
|
||||
replace: async () => {
|
||||
await queryClient.setQueryData(queryKey, data);
|
||||
},
|
||||
append: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(queryKey, (prev) => [
|
||||
...(Array.isArray(prev) ? prev : []),
|
||||
data
|
||||
]);
|
||||
},
|
||||
prepend: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(queryKey, (prev) => [
|
||||
data,
|
||||
...(Array.isArray(prev) ? prev : [])
|
||||
]);
|
||||
},
|
||||
merge: async () => {
|
||||
if (typeof data === 'object' && data !== null && 'id' in data) {
|
||||
await queryClient.setQueryData<Record<string, InferBusterSocketResponseData<TRoute>>>(
|
||||
queryKey,
|
||||
(prev) => ({
|
||||
...(prev || {}),
|
||||
[(data as { id: string }).id]: data
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateStrategy = strategies[strategy as Exclude<QueryDataStrategy, 'ignore'>];
|
||||
|
||||
if (updateStrategy) {
|
||||
await updateStrategy();
|
||||
}
|
||||
};
|
|
@ -3,7 +3,7 @@ import {
|
|||
BusterSocketResponse,
|
||||
BusterSocketResponseRoute
|
||||
} from '@/api/buster_socket';
|
||||
import { QueryKey, useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useBusterWebSocket } from '@/context/BusterWebSocket';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import {
|
||||
|
@ -13,105 +13,39 @@ import {
|
|||
BusterSocketRequestConfig,
|
||||
BusterSocketRequestRoute
|
||||
} from './types';
|
||||
import { ShareAssetType, BusterUserFavorite } from '@/api/asset_interfaces';
|
||||
import { SocketQueryMutationOptions } from './mutationTypes';
|
||||
import { executeQueryDataStrategy } from './queryDataStrategies';
|
||||
import { createQueryKey } from './helpers';
|
||||
|
||||
type QueryDataStrategy = 'replace' | 'append' | 'prepend' | 'merge' | 'ignore';
|
||||
|
||||
// For array items, each item's callback data type is inferred from its responseRoute
|
||||
type PreSetQueryDataItem<TVariables> = {
|
||||
[Route in BusterSocketResponseRoute]: {
|
||||
responseRoute: Route;
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<Route>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<Route>;
|
||||
};
|
||||
}[BusterSocketResponseRoute];
|
||||
|
||||
// For single items, callback data type is either from provided responseRoute or main route
|
||||
type SinglePreSetQueryDataItem<TRoute extends BusterSocketResponseRoute, TVariables> = {
|
||||
requestRoute?: BusterSocketRequestRoute;
|
||||
callback: (
|
||||
data: InferBusterSocketResponseData<TRoute>,
|
||||
variables: TVariables
|
||||
) => InferBusterSocketResponseData<TRoute>;
|
||||
};
|
||||
|
||||
type SocketQueryMutationOptions<
|
||||
TRoute extends BusterSocketResponseRoute,
|
||||
TError,
|
||||
TVariables
|
||||
> = Omit<
|
||||
UseMutationOptions<InferBusterSocketResponseData<TRoute>, TError, TVariables>,
|
||||
'mutationFn'
|
||||
> & {
|
||||
/**
|
||||
* Configuration for optimistically updating query data before the mutation completes.
|
||||
* Can be either a single item or an array of items.
|
||||
*
|
||||
* For array items: callback data type is inferred from each item's responseRoute
|
||||
* For single item:
|
||||
* - If responseRoute provided: callback data type is inferred from that route
|
||||
* - If no responseRoute: callback data type is inferred from main response route
|
||||
*
|
||||
* @example
|
||||
* // Single item without responseRoute (uses main response route's type)
|
||||
* useSocketQueryMutation(
|
||||
* { route: '/users/favorites/post' },
|
||||
* { route: '/users/favorites/post:createFavorite' },
|
||||
* {
|
||||
* preSetQueryData: {
|
||||
* callback: (existingFavorites, variables) => [...(existingFavorites || []), variables]
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
preSetQueryData?:
|
||||
| Array<PreSetQueryDataItem<TVariables>>
|
||||
| SinglePreSetQueryDataItem<TRoute, TVariables>;
|
||||
|
||||
/**
|
||||
* When true, adds a small delay before applying preSetQueryData to ensure React Query's cache
|
||||
* is properly initialized. This is useful when the mutation is called immediately after
|
||||
* component mount and you need to ensure the cache exists before updating it.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
awaitPrefetchQueryData?: boolean;
|
||||
|
||||
/**
|
||||
* Determines how the mutation response data should be integrated into existing query data.
|
||||
*
|
||||
* @example
|
||||
* // Example: Append new item to a list
|
||||
* queryDataStrategy: 'append'
|
||||
*
|
||||
* @property 'replace' - Completely replace existing data with new data
|
||||
* @property 'append' - Add new data to the end of an existing array
|
||||
* @property 'prepend' - Add new data to the beginning of an existing array
|
||||
* @property 'merge' - Merge new data with existing data (useful for objects with IDs)
|
||||
* @property 'ignore' - Don't automatically update query data
|
||||
*
|
||||
* @default 'ignore'
|
||||
*/
|
||||
queryDataStrategy?: QueryDataStrategy;
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that creates a mutation for emitting socket requests and handling responses.
|
||||
* Supports optimistic updates and various strategies for updating the query cache.
|
||||
*
|
||||
* @template TRoute - The type of socket response route
|
||||
* @template TData - The type of data returned by the socket response
|
||||
* @template TVariables - The type of variables passed to the mutation function
|
||||
* @template TError - The type of error that can occur
|
||||
* @template TRequestRoute - The socket request route type
|
||||
* @template TRoute - The socket response route type
|
||||
* @template TError - The error type that can occur
|
||||
* @template TVariables - The variables type passed to the mutation function
|
||||
*
|
||||
* @param socketRequest - The base socket request to emit (variables will be merged with this)
|
||||
* @param socketResponse - Configuration for the socket response including route and error handler
|
||||
* @param options - Additional options for the React Query mutation hook
|
||||
* @param socketRequest - The base socket request configuration
|
||||
* @param socketResponse - The socket response configuration with optional error handler
|
||||
* @param options - Additional options for configuring the mutation behavior
|
||||
*
|
||||
* @returns A React Query mutation result for handling socket requests
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { mutate } = useSocketQueryMutation(
|
||||
* { route: '/users/favorites/post' },
|
||||
* { route: '/users/favorites/post:createFavorite' },
|
||||
* {
|
||||
* preSetQueryData: [
|
||||
* {
|
||||
* responseRoute: '/users/favorites/list:listFavorites',
|
||||
* callback: (data, variables) => [...(data || []), variables]
|
||||
* }
|
||||
* ],
|
||||
* queryDataStrategy: 'append'
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export const useSocketQueryMutation = <
|
||||
TRequestRoute extends BusterSocketRequestRoute,
|
||||
|
@ -129,45 +63,26 @@ export const useSocketQueryMutation = <
|
|||
const queryClient = useQueryClient();
|
||||
const { preSetQueryData, queryDataStrategy = 'ignore', ...mutationOptions } = options || {};
|
||||
|
||||
const updateQueryData = useMemoizedFn(
|
||||
async (queryKey: QueryKey, data: InferBusterSocketResponseData<TRoute>) => {
|
||||
if (queryDataStrategy === 'ignore') return;
|
||||
const handlePreSetQueryData = useMemoizedFn(async (variables: TVariables) => {
|
||||
if (!preSetQueryData) return;
|
||||
|
||||
const strategies: Record<Exclude<QueryDataStrategy, 'ignore'>, () => Promise<void>> = {
|
||||
replace: async () => {
|
||||
await queryClient.setQueryData(queryKey, data);
|
||||
},
|
||||
append: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(
|
||||
queryKey,
|
||||
(prev) => [...(Array.isArray(prev) ? prev : []), data]
|
||||
);
|
||||
},
|
||||
prepend: async () => {
|
||||
await queryClient.setQueryData<InferBusterSocketResponseData<TRoute>[]>(
|
||||
queryKey,
|
||||
(prev) => [data, ...(Array.isArray(prev) ? prev : [])]
|
||||
);
|
||||
},
|
||||
merge: async () => {
|
||||
if (typeof data === 'object' && data !== null && 'id' in data) {
|
||||
await queryClient.setQueryData<Record<string, InferBusterSocketResponseData<TRoute>>>(
|
||||
queryKey,
|
||||
(prev) => ({
|
||||
...(prev || {}),
|
||||
[(data as { id: string }).id]: data
|
||||
})
|
||||
);
|
||||
if (options?.awaitPrefetchQueryData) {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateStrategy = strategies[queryDataStrategy as Exclude<QueryDataStrategy, 'ignore'>];
|
||||
if (updateStrategy) {
|
||||
await updateStrategy();
|
||||
const arrayOfPreSetQueryData = Array.isArray(preSetQueryData)
|
||||
? preSetQueryData
|
||||
: [{ ...preSetQueryData, responseRoute: socketResponse.route }];
|
||||
|
||||
for (const item of arrayOfPreSetQueryData) {
|
||||
const { responseRoute, requestRoute, callback } = item!;
|
||||
const requestPayload: undefined | BusterSocketRequest = requestRoute
|
||||
? ({ route: requestRoute, payload: variables } as BusterSocketRequest)
|
||||
: undefined;
|
||||
const presetQueryKey = createQueryKey({ route: responseRoute! }, requestPayload);
|
||||
await queryClient.setQueryData(presetQueryKey, (prev: any) => callback(prev, variables));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const mutationFn = useMemoizedFn(async (variables: TVariables) => {
|
||||
const request = {
|
||||
|
@ -176,33 +91,7 @@ export const useSocketQueryMutation = <
|
|||
} as BusterSocketRequest;
|
||||
|
||||
const queryKey = createQueryKey(socketResponse, request);
|
||||
const arrayOfPreSetQueryData = Array.isArray(preSetQueryData)
|
||||
? preSetQueryData
|
||||
: preSetQueryData
|
||||
? [
|
||||
{
|
||||
...preSetQueryData,
|
||||
responseRoute: socketResponse.route
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
if (preSetQueryData && arrayOfPreSetQueryData.filter(Boolean).length > 0) {
|
||||
if (options?.awaitPrefetchQueryData) {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
for (const item of arrayOfPreSetQueryData) {
|
||||
const { responseRoute, requestRoute, callback } = item!;
|
||||
const requestPayload: undefined | BusterSocketRequest = requestRoute
|
||||
? ({ route: requestRoute, payload: request.payload } as BusterSocketRequest)
|
||||
: undefined;
|
||||
const presetQueryKey = createQueryKey({ route: responseRoute! }, requestPayload);
|
||||
await queryClient.setQueryData(presetQueryKey, (prev: any) => {
|
||||
return callback(prev, variables);
|
||||
});
|
||||
}
|
||||
}
|
||||
await handlePreSetQueryData(variables);
|
||||
|
||||
const response = await busterSocket.emitAndOnce({
|
||||
emitEvent: request,
|
||||
|
@ -216,7 +105,12 @@ export const useSocketQueryMutation = <
|
|||
});
|
||||
|
||||
if (response !== undefined) {
|
||||
await updateQueryData(queryKey, response as InferBusterSocketResponseData<TRoute>);
|
||||
await executeQueryDataStrategy(
|
||||
queryClient,
|
||||
queryKey as unknown[],
|
||||
response as InferBusterSocketResponseData<TRoute>,
|
||||
queryDataStrategy
|
||||
);
|
||||
}
|
||||
|
||||
return response as InferBusterSocketResponseData<TRoute>;
|
||||
|
@ -227,66 +121,3 @@ export const useSocketQueryMutation = <
|
|||
mutationFn
|
||||
});
|
||||
};
|
||||
|
||||
const Example = () => {
|
||||
// Example: Favorites mutation with multiple preSetQueryData updates
|
||||
const { mutate } = useSocketQueryMutation(
|
||||
{ route: '/users/favorites/post' },
|
||||
{ route: '/users/favorites/post:createFavorite' },
|
||||
{
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/users/favorites/list:listFavorites',
|
||||
callback: (data, variables) => {
|
||||
const favorites = Array.isArray(data) ? data : [];
|
||||
return [variables, ...favorites];
|
||||
}
|
||||
},
|
||||
{
|
||||
responseRoute: '/users/favorites/post:createFavorite',
|
||||
callback: (_: unknown, variables: BusterUserFavorite) => {
|
||||
return [variables];
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
mutate({
|
||||
id: 'some-asset-id',
|
||||
asset_type: ShareAssetType.DASHBOARD,
|
||||
name: 'some-title'
|
||||
});
|
||||
|
||||
const { mutate: mutate2 } = useSocketQueryMutation(
|
||||
{ route: '/dashboards/delete' },
|
||||
{ route: '/dashboards/delete:deleteDashboard' },
|
||||
{
|
||||
preSetQueryData: [
|
||||
{
|
||||
responseRoute: '/chats/list:getChatsList',
|
||||
callback: (data, variables) => {
|
||||
data[0].id;
|
||||
return [...(data || [])];
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: mutate3 } = useSocketQueryMutation(
|
||||
{ route: '/dashboards/delete' },
|
||||
{ route: '/chats/get:getChat' },
|
||||
{
|
||||
preSetQueryData: {
|
||||
// responseRoute: '/chats/list:getChatsList',
|
||||
callback: (data, variables) => {
|
||||
data.id;
|
||||
return { ...data };
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue