mirror of https://github.com/langgenius/dify.git
4.1 KiB
4.1 KiB
Runtime Rules
Table of Contents
- Conditional queries
- Cache invalidation
- Key API guide
mutatevsmutateAsync- Legacy migration
Conditional Queries
Prefer contract-shaped queryOptions(...).
When required input is missing, prefer input: skipToken instead of placeholder params or non-null assertions.
Use enabled only for extra business gating after the input itself is already valid.
import { skipToken, useQuery } from '@tanstack/react-query'
// Disable the query by skipping input construction.
function useAccessMode(appId: string | undefined) {
return useQuery(consoleQuery.accessControl.appAccessMode.queryOptions({
input: appId
? { params: { appId } }
: skipToken,
}))
}
// Avoid runtime-only guards that bypass type checking.
function useBadAccessMode(appId: string | undefined) {
return useQuery(consoleQuery.accessControl.appAccessMode.queryOptions({
input: { params: { appId: appId! } },
enabled: !!appId,
}))
}
Cache Invalidation
Bind invalidation in the service-layer mutation definition. Components may add UI feedback in call-site callbacks, but they should not decide which queries to invalidate.
Use:
.key()for namespace or prefix invalidation.queryKey(...)only for exact cache reads or writes such asgetQueryDataandsetQueryDataqueryClient.invalidateQueries(...)in mutationonSuccess
Do not use deprecated useInvalid from use-base.ts.
// Service layer owns cache invalidation.
export const useUpdateAccessMode = () => {
const queryClient = useQueryClient()
return useMutation(consoleQuery.accessControl.updateAccessMode.mutationOptions({
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: consoleQuery.accessControl.appWhitelistSubjects.key(),
})
},
}))
}
// Component only adds UI behavior.
updateAccessMode({ appId, mode }, {
onSuccess: () => Toast.notify({ type: 'success', message: '...' }),
})
// Avoid putting invalidation knowledge in the component.
mutate({ appId, mode }, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: consoleQuery.accessControl.appWhitelistSubjects.key(),
})
},
})
Key API Guide
.key(...)- Use for partial matching operations.
- Prefer it for invalidation, refetch, and cancel patterns.
- Example:
queryClient.invalidateQueries({ queryKey: consoleQuery.billing.key() })
.queryKey(...)- Use for a specific query's full key.
- Prefer it for exact cache addressing and direct reads or writes.
.mutationKey(...)- Use for a specific mutation's full key.
- Prefer it for mutation defaults registration, mutation-status filtering, and devtools grouping.
mutate vs mutateAsync
Prefer mutate by default.
Use mutateAsync only when Promise semantics are truly required, such as parallel mutations or sequential steps with result dependencies.
Rules:
- Event handlers should usually call
mutate(...)withonSuccessoronError. - Every
await mutateAsync(...)must be wrapped intry/catch. - Do not use
mutateAsyncwhen callbacks already express the flow clearly.
// Default case.
mutation.mutate(data, {
onSuccess: result => router.push(result.url),
})
// Promise semantics are required.
try {
const order = await createOrder.mutateAsync(orderData)
await confirmPayment.mutateAsync({ orderId: order.id, token })
router.push(`/orders/${order.id}`)
}
catch (error) {
Toast.notify({
type: 'error',
message: error instanceof Error ? error.message : 'Unknown error',
})
}
Legacy Migration
When touching old code, migrate it toward these rules:
| Old pattern | New pattern |
|---|---|
useInvalid(key) in service layer |
queryClient.invalidateQueries(...) inside mutation onSuccess |
| component-triggered invalidation after mutation | move invalidation into the service-layer mutation definition |
| imperative fetch plus manual invalidation | wrap it in useMutation(...mutationOptions(...)) |
await mutateAsync() without try/catch |
switch to mutate(...) or add try/catch |