docs/addons/shared-query-client-design.md
This document describes the design for sharing the React Query client between the main Wealthfolio application and addons, enabling automatic cache invalidation and data synchronization.
Instead of each addon creating its own QueryClient, addons now share the main application's QueryClient:
Before:
// Each addon had its own QueryClient
const queryClient = new QueryClient({
/* config */
});
After:
// Addons use the shared QueryClient from main app
const sharedQueryClient = ctx.api.query.getClient();
The main app exposes its QueryClient globally:
// In App.tsx
const [queryClient] = useState(
() =>
new QueryClient({
/* config */
}),
);
(window as any).__wealthfolio_query_client__ = queryClient;
Both the main app and addons use the same query keys to ensure cache consistency:
// Shared in @wealthfolio/addon-sdk
export const QueryKeys = {
GOALS: "goals",
GOALS_ALLOCATIONS: "goals_allocations",
ACCOUNTS: "accounts",
// ... other keys
} as const;
Addons have access to query management functions:
interface QueryAPI {
getClient(): QueryClient;
invalidateQueries(queryKey: string | string[]): void;
refetchQueries(queryKey: string | string[]): void;
}
The goal progress tracker addon demonstrates this pattern:
// Simple hook using shared query keys - no event listeners needed
export function useGoals({ ctx, enabled = true }: UseGoalsOptions) {
return useQuery<Goal[]>({
queryKey: [QueryKeys.GOALS], // Shared query key
queryFn: async () => {
const data = await ctx.api.goals.getAll();
return data || [];
},
enabled: enabled && !!ctx.api,
staleTime: 10 * 60 * 1000, // 10 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});
}
const AddonWrapper = () => {
const sharedQueryClient = ctx.api.query.getClient();
return (
<QueryClientProvider client={sharedQueryClient}>
<AddonComponent ctx={ctx} />
</QueryClientProvider>
);
};
QueryKeys.GOALS cache automatically (via React
Query mutation)With the shared query client approach, cache invalidation happens automatically:
Always import and use QueryKeys from the addon SDK:
import { QueryKeys } from "@wealthfolio/addon-sdk";
Set up event listeners in your hooks to automatically invalidate cache when data changes:
useEffect(() => {
const unlisten = await ctx.api.events.goals.onCreate(() => {
ctx.api.query.invalidateQueries([QueryKeys.GOALS]);
});
return unlisten;
}, [ctx]);
Always use the shared QueryClient for consistency:
const queryClient = ctx.api.query.getClient();
Use descriptive and consistent cache keys that match the main app's patterns:
queryKey: [QueryKeys.GOALS];
queryKey: [QueryKeys.latestValuations, accountIds];
This design provides a robust foundation for data synchronization between the main application and addons, ensuring a seamless user experience across all components of the Wealthfolio ecosystem.