State Management
Read and write your app's data through a typed REST client wrapped in small hooks. TanStack Query (React Query) handles caching, loading flags, refetching, and invalidation, so you don't manage server state by hand. Every resource you define is reachable this way.
This page covers four tools:
- The aepbase client — read and write resources.
- Query keys — name your cache slots consistently.
- Data hooks — the read + mutate pattern.
- Flags and settings — household- and per-user state.
The aepbase client
Read and write resources with the aepbase client from @rambleraptor/homestead-core/api/aepbase. Pass the collection's plural (import the constant from the app's resources.ts).
import { aepbase } from '@rambleraptor/homestead-core/api/aepbase';
import { GIFT_CARDS } from '../resources';
await aepbase.list<GiftCard>(GIFT_CARDS); // GET collection (auto-paginates)
await aepbase.get<GiftCard>(GIFT_CARDS, id); // GET one
await aepbase.create<GiftCard>(GIFT_CARDS, body); // POST (FormData for file fields)
await aepbase.update<GiftCard>(GIFT_CARDS, id, body); // PATCH (merge — send only changed fields)
await aepbase.remove(GIFT_CARDS, id); // DELETEFor a child resource, pass the parent path:
await aepbase.list<Transaction>('transactions', {
parent: ['gift-cards', cardId], // → /gift-cards/{cardId}/transactions
});getCurrentUser() gives you the signed-in user, handy for stamping a created_by field as users/{id}.
Query keys
Name your cache slots with queryKeys from @rambleraptor/homestead-core/api/queryClient. It builds hierarchical keys so reads and invalidations line up:
import { queryKeys } from '@rambleraptor/homestead-core/api/queryClient';
queryKeys.app('gift-cards').all(); // everything for the app
queryKeys.app('gift-cards').resource('gift-card').list(); // a collection
queryKeys.app('gift-cards').resource('gift-card').detail(id); // one recordInvalidate at the level you need. Invalidating .all() after a write refreshes every list and detail view in the app.
Data hooks
Write one small hook per operation in your app's hooks/ folder. A read is a useQuery:
// packages/homestead-apps/gift-cards/hooks/useGiftCards.ts
import { useQuery } from '@tanstack/react-query';
import { queryKeys } from '@rambleraptor/homestead-core/api/queryClient';
import { aepbase } from '@rambleraptor/homestead-core/api/aepbase';
import { GIFT_CARDS } from '../resources';
export function useGiftCards() {
return useQuery({
queryKey: queryKeys.app('gift-cards').resource('gift-card').list(),
queryFn: () => aepbase.list<GiftCard>(GIFT_CARDS),
});
}A write is a useMutation that invalidates the relevant keys onSuccess, so any component reading that data updates itself:
// packages/homestead-apps/gift-cards/hooks/useCreateGiftCard.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { queryKeys } from '@rambleraptor/homestead-core/api/queryClient';
import { aepbase } from '@rambleraptor/homestead-core/api/aepbase';
import { GIFT_CARDS } from '../resources';
export function useCreateGiftCard() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: GiftCardFormData) =>
aepbase.create<GiftCard>(GIFT_CARDS, data),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: queryKeys.app('gift-cards').all(),
}),
});
}Components stay declarative: const { data, isLoading } = useGiftCards() for reads, const { mutateAsync, isPending } = useCreateGiftCard() for writes.
Skip the boilerplate for plain CRUD
For a resource that needs no custom logic, use the generic resource hooks:
import { useResourceCreate } from '@rambleraptor/homestead-core/api/resourceHooks';
export const useCreateCreditCard = () =>
useResourceCreate<CreditCard, CreditCardFormData>('credit-cards', 'credit-card');They wire up the create/update/delete call and the cache invalidation for you. Reach for a hand-written useMutation only when a write needs extra steps (file handling, nested writes, name resolution).
Flags and settings
Store small settings without defining a resource. Homestead has two stores for this, each read and written with a single hook:
App flags — one value shared across the whole household (feature toggles, defaults). Read with
useAppFlag. See App Flags.tsconst { value, setValue } = useAppFlag<string>('groceries', 'default_store');User settings — one value per user (a personal preference). Declare a
userSettingsmap on your app config, then read withuseUserSettingfrom@rambleraptor/homestead-core/user-settings.tsconst { value, setValue } = useUserSetting<'google' | 'apple'>('people', 'map_provider');
Both guarantee the declared default (never undefined once you set one) and persist the change for you. Use a resource for lists of records, an app flag for one household-wide value, and a user setting for a per-person preference.