fix: Stores Not Populating Sometimes (#6266)

This commit is contained in:
Michael Genson
2025-09-27 19:17:08 -05:00
committed by GitHub
parent e3f120c680
commit 824603a578
13 changed files with 31 additions and 27 deletions

View File

@@ -1,10 +1,10 @@
import { useAsyncKey } from "../use-utils"; import type { AsyncData, NuxtError } from "#app";
import type { BoundT } from "./types"; import type { BoundT } from "./types";
import type { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients"; import type { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
import type { QueryValue } from "~/lib/api/base/route"; import type { QueryValue } from "~/lib/api/base/route";
interface ReadOnlyStoreActions<T extends BoundT> { interface ReadOnlyStoreActions<T extends BoundT> {
getAll(page?: number, perPage?: number, params?: any): Ref<T[] | null>; getAll(page?: number, perPage?: number, params?: any): AsyncData<T[] | null, NuxtError<unknown> | null>;
refresh(page?: number, perPage?: number, params?: any): Promise<void>; refresh(page?: number, perPage?: number, params?: any): Promise<void>;
} }
@@ -21,6 +21,7 @@ interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
* a lot of refreshing hooks to be called on operations * a lot of refreshing hooks to be called on operations
*/ */
export function useReadOnlyActions<T extends BoundT>( export function useReadOnlyActions<T extends BoundT>(
storeKey: string,
api: BaseCRUDAPIReadOnly<T>, api: BaseCRUDAPIReadOnly<T>,
allRef: Ref<T[] | null> | null, allRef: Ref<T[] | null> | null,
loading: Ref<boolean>, loading: Ref<boolean>,
@@ -29,7 +30,7 @@ export function useReadOnlyActions<T extends BoundT>(
params.orderBy ??= "name"; params.orderBy ??= "name";
params.orderDirection ??= "asc"; params.orderDirection ??= "asc";
const allItems = useAsyncData(useAsyncKey(), async () => { const allItems = useAsyncData(storeKey, async () => {
loading.value = true; loading.value = true;
try { try {
const { data } = await api.getAll(page, perPage, params); const { data } = await api.getAll(page, perPage, params);
@@ -80,6 +81,7 @@ export function useReadOnlyActions<T extends BoundT>(
* a lot of refreshing hooks to be called on operations * a lot of refreshing hooks to be called on operations
*/ */
export function useStoreActions<T extends BoundT>( export function useStoreActions<T extends BoundT>(
storeKey: string,
api: BaseCRUDAPI<unknown, T, unknown>, api: BaseCRUDAPI<unknown, T, unknown>,
allRef: Ref<T[] | null> | null, allRef: Ref<T[] | null> | null,
loading: Ref<boolean>, loading: Ref<boolean>,
@@ -88,7 +90,7 @@ export function useStoreActions<T extends BoundT>(
params.orderBy ??= "name"; params.orderBy ??= "name";
params.orderDirection ??= "asc"; params.orderDirection ??= "asc";
const allItems = useAsyncData(useAsyncKey(), async () => { const allItems = useAsyncData(storeKey, async () => {
loading.value = true; loading.value = true;
try { try {
const { data } = await api.getAll(page, perPage, params); const { data } = await api.getAll(page, perPage, params);

View File

@@ -13,12 +13,13 @@ export const useData = function <T extends BoundT>(defaultObject: T) {
}; };
export const useReadOnlyStore = function <T extends BoundT>( export const useReadOnlyStore = function <T extends BoundT>(
storeKey: string,
store: Ref<T[]>, store: Ref<T[]>,
loading: Ref<boolean>, loading: Ref<boolean>,
api: BaseCRUDAPIReadOnly<T>, api: BaseCRUDAPIReadOnly<T>,
params = {} as Record<string, QueryValue>, params = {} as Record<string, QueryValue>,
) { ) {
const storeActions = useReadOnlyActions(api, store, loading); const storeActions = useReadOnlyActions(`${storeKey}-store-readonly`, api, store, loading);
const actions = { const actions = {
...storeActions, ...storeActions,
async refresh() { async refresh() {
@@ -29,21 +30,22 @@ export const useReadOnlyStore = function <T extends BoundT>(
}, },
}; };
// initial hydration
if (!loading.value && !store.value.length) { if (!loading.value && !store.value.length) {
const result = actions.getAll(1, -1, params); actions.refresh();
store.value = result.value || [];
} }
return { store, actions }; return { store, actions };
}; };
export const useStore = function <T extends BoundT>( export const useStore = function <T extends BoundT>(
storeKey: string,
store: Ref<T[]>, store: Ref<T[]>,
loading: Ref<boolean>, loading: Ref<boolean>,
api: BaseCRUDAPI<unknown, T, unknown>, api: BaseCRUDAPI<unknown, T, unknown>,
params = {} as Record<string, QueryValue>, params = {} as Record<string, QueryValue>,
) { ) {
const storeActions = useStoreActions(api, store, loading); const storeActions = useStoreActions(`${storeKey}-store`, api, store, loading);
const actions = { const actions = {
...storeActions, ...storeActions,
async refresh() { async refresh() {
@@ -54,9 +56,9 @@ export const useStore = function <T extends BoundT>(
}, },
}; };
// initial hydration
if (!loading.value && !store.value.length) { if (!loading.value && !store.value.length) {
const result = actions.getAll(1, -1, params); actions.refresh();
store.value = result.value || [];
} }
return { store, actions }; return { store, actions };

View File

@@ -17,10 +17,10 @@ export const useCategoryData = function () {
export const useCategoryStore = function (i18n?: Composer) { export const useCategoryStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<RecipeCategory>(store, loading, api.categories); return useStore<RecipeCategory>("category", store, loading, api.categories);
}; };
export const usePublicCategoryStore = function (groupSlug: string, i18n?: Composer) { export const usePublicCategoryStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeCategory>(store, publicLoading, api.categories); return useReadOnlyStore<RecipeCategory>("category", store, publicLoading, api.categories);
}; };

View File

@@ -9,7 +9,7 @@ const publicLoading = ref(false);
export const useCookbookStore = function (i18n?: Composer) { export const useCookbookStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
const store = useStore<ReadCookBook>(cookbooks, loading, api.cookbooks); const store = useStore<ReadCookBook>("cookbook", cookbooks, loading, api.cookbooks);
const updateAll = async function (updateData: UpdateCookBook[]) { const updateAll = async function (updateData: UpdateCookBook[]) {
loading.value = true; loading.value = true;
@@ -25,5 +25,5 @@ export const useCookbookStore = function (i18n?: Composer) {
export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) { export const usePublicCookbookStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<ReadCookBook>(cookbooks, publicLoading, api.cookbooks); return useReadOnlyStore<ReadCookBook>("cookbook", cookbooks, publicLoading, api.cookbooks);
}; };

View File

@@ -18,10 +18,10 @@ export const useFoodData = function () {
export const useFoodStore = function (i18n?: Composer) { export const useFoodStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<IngredientFood>(store, loading, api.foods); return useStore<IngredientFood>("food", store, loading, api.foods);
}; };
export const usePublicFoodStore = function (groupSlug: string, i18n?: Composer) { export const usePublicFoodStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<IngredientFood>(store, publicLoading, api.foods); return useReadOnlyStore<IngredientFood>("food", store, publicLoading, api.foods);
}; };

View File

@@ -9,10 +9,10 @@ const publicLoading = ref(false);
export const useHouseholdStore = function (i18n?: Composer) { export const useHouseholdStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useReadOnlyStore<HouseholdSummary>(store, loading, api.households); return useReadOnlyStore<HouseholdSummary>("household", store, loading, api.households);
}; };
export const usePublicHouseholdStore = function (groupSlug: string, i18n?: Composer) { export const usePublicHouseholdStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<HouseholdSummary>(store, publicLoading, api.households); return useReadOnlyStore<HouseholdSummary>("household-public", store, publicLoading, api.households);
}; };

View File

@@ -17,5 +17,5 @@ export const useLabelData = function () {
export const useLabelStore = function (i18n?: Composer) { export const useLabelStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<MultiPurposeLabelOut>(store, loading, api.multiPurposeLabels); return useStore<MultiPurposeLabelOut>("label", store, loading, api.multiPurposeLabels);
}; };

View File

@@ -17,10 +17,10 @@ export const useTagData = function () {
export const useTagStore = function (i18n?: Composer) { export const useTagStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<RecipeTag>(store, loading, api.tags); return useStore<RecipeTag>("tag", store, loading, api.tags);
}; };
export const usePublicTagStore = function (groupSlug: string, i18n?: Composer) { export const usePublicTagStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeTag>(store, publicLoading, api.tags); return useReadOnlyStore<RecipeTag>("tag", store, publicLoading, api.tags);
}; };

View File

@@ -23,10 +23,10 @@ export const useToolData = function () {
export const useToolStore = function (i18n?: Composer) { export const useToolStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<RecipeTool>(store, loading, api.tools); return useStore<RecipeTool>("tool", store, loading, api.tools);
}; };
export const usePublicToolStore = function (groupSlug: string, i18n?: Composer) { export const usePublicToolStore = function (groupSlug: string, i18n?: Composer) {
const api = usePublicExploreApi(groupSlug, i18n).explore; const api = usePublicExploreApi(groupSlug, i18n).explore;
return useReadOnlyStore<RecipeTool>(store, publicLoading, api.tools); return useReadOnlyStore<RecipeTool>("tool", store, publicLoading, api.tools);
}; };

View File

@@ -18,5 +18,5 @@ export const useUnitData = function () {
export const useUnitStore = function (i18n?: Composer) { export const useUnitStore = function (i18n?: Composer) {
const api = useUserApi(i18n); const api = useUserApi(i18n);
return useStore<IngredientUnit>(store, loading, api.units); return useStore<IngredientUnit>("unit", store, loading, api.units);
}; };

View File

@@ -16,5 +16,5 @@ export const useUserStore = function (i18n?: Composer) {
const requests = useRequests(i18n); const requests = useRequests(i18n);
const api = new GroupUserAPIReadOnly(requests); const api = new GroupUserAPIReadOnly(requests);
return useReadOnlyStore<UserSummary>(store, loading, api, { orderBy: "full_name" }); return useReadOnlyStore<UserSummary>("user", store, loading, api, { orderBy: "full_name" });
}; };

View File

@@ -78,7 +78,7 @@ export const useGroupRecipeActions = function (
}; };
const actions = { const actions = {
...useStoreActions<GroupRecipeActionOut>(api.groupRecipeActions, groupRecipeActions, loading), ...useStoreActions<GroupRecipeActionOut>("group-recipe-actions", api.groupRecipeActions, groupRecipeActions, loading),
flushStore() { flushStore() {
groupRecipeActions.value = []; groupRecipeActions.value = [];
}, },

View File

@@ -8,7 +8,7 @@ export const useToggleDarkMode = () => {
}; };
export const useAsyncKey = function () { export const useAsyncKey = function () {
return String(Date.now()); return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
}; };
export const titleCase = function (str: string) { export const titleCase = function (str: string) {