menu-data.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. // packages/app/context/menu-data.js
  2. import { create } from 'zustand';
  3. import { getDataForMenu } from 'app/lib/util';
  4. import { useEffect, useCallback } from 'react';
  5. /** Menu object id (string) or a UNA menu request object. */
  6. export type MenuObject = string | Record<string, unknown>;
  7. type MenuEntry = {
  8. /** Menu payload, or `false` while missing / after a failed first load. */
  9. data: any;
  10. fetched: boolean;
  11. loading: boolean;
  12. promise: Promise<any> | null;
  13. error?: unknown;
  14. };
  15. type MenuDataStore = {
  16. menus: Record<string, MenuEntry>;
  17. /** Load (or reuse) a menu; `force` refetches. Resolves to the data or `false`. */
  18. fetchMenu: (object: MenuObject, force?: boolean) => Promise<any>;
  19. };
  20. export const useMenuDataStore = create<MenuDataStore>()((set, get) => ({
  21. menus: {},
  22. fetchMenu: async (object, force = false) => {
  23. const key = typeof object === 'string' ? object : JSON.stringify(object);
  24. const existing = get().menus[key];
  25. if (existing?.data && !force) return existing.data;
  26. if (existing?.promise && !force) return existing.promise;
  27. const promise: Promise<any> = getDataForMenu({ object, params: null }, (data: any) => data)
  28. .then((data: any) => {
  29. set((state) => ({
  30. menus: {
  31. ...state.menus,
  32. [key]: { data, fetched: true, loading: false, promise: null },
  33. },
  34. }));
  35. return data;
  36. })
  37. .catch((error: unknown) => {
  38. set((state) => ({
  39. menus: {
  40. ...state.menus,
  41. [key]: {
  42. data: existing?.data ?? false,
  43. fetched: true,
  44. loading: false,
  45. promise: null,
  46. error,
  47. },
  48. },
  49. }));
  50. return existing?.data ?? false;
  51. });
  52. set((state) => ({
  53. menus: {
  54. ...state.menus,
  55. [key]: {
  56. data: existing?.data ?? false,
  57. fetched: Boolean(existing?.data),
  58. loading: true,
  59. promise,
  60. error: null,
  61. },
  62. },
  63. }));
  64. return promise;
  65. },
  66. }));
  67. export const useMenuData = (object: MenuObject | null | undefined) => {
  68. const key = typeof object === 'string' ? object : JSON.stringify(object);
  69. const data = useMenuDataStore((state) => state.menus[key]?.data ?? false);
  70. const isLoading = useMenuDataStore((state) => state.menus[key]?.loading ?? false);
  71. const isFetched = useMenuDataStore((state) => state.menus[key]?.fetched ?? false);
  72. const fetchMenu = useMenuDataStore((state) => state.fetchMenu);
  73. useEffect(() => {
  74. if (object) fetchMenu(object);
  75. }, [object, fetchMenu]);
  76. const refetch = useCallback(() => {
  77. if (object) fetchMenu(object, true);
  78. }, [object, fetchMenu]);
  79. return { menuData: data, isFetched, isLoading, refetch };
  80. };