diff --git a/docs/pages/01_ui_react.adoc b/docs/pages/01_ui_react.adoc index a14cee1b..fc7ae2a5 100644 --- a/docs/pages/01_ui_react.adoc +++ b/docs/pages/01_ui_react.adoc @@ -81,6 +81,22 @@ export function SomeAction() { > Certain routes may require a `signedIdentifier` param, but that's okay because if we miss it out, we get IDE errors, and compiler errors as well. +== Hooks + +=== useViewData + +The `useViewData()` hook can be used to obtain the latest view page/dialog's latest data. + +*Available methods:* + +- getLatestViewData(): Returns the data for the last loaded View page/dialog. +- setLatestViewData(): Used internally only to update the data. Should not be used in application code. + +[WARN] +==== +If for whatever reason the data's owning View fails it's refresh request, the `getLatestViewData()` method return `null` +==== + == Opt-in features === Generate pro+ MUI code diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs index 7b8fd121..ecc5104f 100644 --- a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs @@ -18,7 +18,7 @@ import { {{# unless page.container.table }}useCallback, useEffect, useRef, {{/ u {{/ if }} import { useJudoNavigation } from '~/components'; import { useConfirmDialog, useDialog, useFilterDialog } from '~/components/dialog'; - import { useSnacks, useCRUDDialog } from '~/hooks'; + import { useSnacks, useCRUDDialog, useViewData } from '~/hooks'; import { {{# if (hasPageRequiredBy page) }}passesLocalValidation,{{/ if }} processQueryCustomizer, @@ -158,6 +158,7 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) { const { navigate, back: navigateBack } = useJudoNavigation(); const { openFilterDialog } = useFilterDialog(); const { openConfirmDialog } = useConfirmDialog(); + const { setLatestViewData } = useViewData(); const handleError = useErrorHandler(); const openCRUDDialog = useCRUDDialog(); const [createDialog, closeDialog] = useDialog(); diff --git a/judo-ui-react/src/main/resources/actor/src/hooks/ViewDataContext.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/hooks/ViewDataContext.tsx.hbs new file mode 100644 index 00000000..3002efa0 --- /dev/null +++ b/judo-ui-react/src/main/resources/actor/src/hooks/ViewDataContext.tsx.hbs @@ -0,0 +1,38 @@ +import type { FC, ReactNode } from 'react'; +import { createContext, useContext, useState } from 'react'; +import type { JudoIdentifiable } from '~/services/data-api/common'; + +interface DataContextProps { + children: ReactNode; +} + +interface ViewContextContextValue { + setLatestViewData: (newData: JudoIdentifiable | null) => void; + getLatestViewData: () => JudoIdentifiable | null; +} + +const ViewDataContext = createContext(undefined); + +export const ViewDataProvider: FC = ({ children }) => { + const [data, setData] = useState | null>(null); + + const useData = (): ViewContextContextValue => { + const setLatestViewData = (newData: JudoIdentifiable | null): void => { + setData(newData); + }; + const getLatestViewData = (): JudoIdentifiable | null => data; + return { setLatestViewData, getLatestViewData }; + }; + + return {children}; +}; + +export const useViewData = (): ViewContextContextValue => { + const context = useContext(ViewDataContext); + + if (!context) { + throw new Error('useViewData must be used within a ViewDataProvider'); + } + + return context; +}; diff --git a/judo-ui-react/src/main/resources/actor/src/hooks/index.ts.hbs b/judo-ui-react/src/main/resources/actor/src/hooks/index.ts.hbs index 0e4d8fc6..e44c7516 100644 --- a/judo-ui-react/src/main/resources/actor/src/hooks/index.ts.hbs +++ b/judo-ui-react/src/main/resources/actor/src/hooks/index.ts.hbs @@ -9,3 +9,4 @@ export * from './useLocalStorage'; export * from './useLogoProps'; export * from './useNavigationConfirmation'; export * from './useSnacks'; +export * from './ViewDataContext'; diff --git a/judo-ui-react/src/main/resources/actor/src/main.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/main.tsx.hbs index 62de92a4..255fc8d9 100644 --- a/judo-ui-react/src/main/resources/actor/src/main.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/main.tsx.hbs @@ -29,7 +29,7 @@ import { accessServiceImpl, judoAxiosProvider } from '~/services/data-axios'; import App from './App'; import { routes } from './routes'; import { RootErrorBoundary } from './components/RootErrorBoundary'; -import { ConfigProvider } from './hooks'; +import { ConfigProvider, ViewDataProvider } from './hooks'; {{# if isMUILicensed }} LicenseInfo.setLicenseKey(import.meta.env.VITE_MUI_LICENSE_KEY); @@ -90,19 +90,21 @@ const FILE_DEFAULT_BASE_URL: string = import.meta.env.VITE_FILE_DEFAULT_BASE_URL - - - {{# if application.authentication }} - - - {{/ if }} - - {{# if application.authentication }} - - - {{/ if }} - - + + + + {{# if application.authentication }} + + + {{/ if }} + + {{# if application.authentication }} + + + {{/ if }} + + + diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs index 4aec21cf..43b577b7 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs @@ -12,6 +12,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (queryCus {{ else }} const result = await {{ getServiceImplForPage page }}.refresh({{{ refreshActionDataParameter action }}}, pageQueryCustomizer); setData(result); + setLatestViewData(result); // re-set payloadDiff payloadDiff.current = { __identifier: result.__identifier, @@ -26,6 +27,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (queryCus {{/ if }} } catch (error) { handleError(error); + setLatestViewData(null); return Promise.reject(error); } finally { setIsLoading(false); diff --git a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs index 6d1704a2..d5dc0751 100644 --- a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs @@ -18,7 +18,7 @@ import { {{# unless page.container.table }}useCallback, useEffect, useRef, {{/ u {{/ if }} import { useJudoNavigation } from '~/components'; import { useConfirmDialog, useFilterDialog } from '~/components/dialog'; - import { useSnacks, useCRUDDialog } from '~/hooks'; + import { useSnacks, useCRUDDialog, useViewData } from '~/hooks'; import { {{# if (hasPageRequiredBy page) }}passesLocalValidation,{{/ if }} processQueryCustomizer, @@ -94,6 +94,7 @@ export default function {{ pageName page }}() { const { navigate, back: navigateBack } = useJudoNavigation(); const { openFilterDialog } = useFilterDialog(); const { openConfirmDialog } = useConfirmDialog(); + const { setLatestViewData } = useViewData(); const handleError = useErrorHandler(); const openCRUDDialog = useCRUDDialog(); diff --git a/judo-ui-react/src/main/resources/ui-react.yaml b/judo-ui-react/src/main/resources/ui-react.yaml index 112ee00b..0007b167 100644 --- a/judo-ui-react/src/main/resources/ui-react.yaml +++ b/judo-ui-react/src/main/resources/ui-react.yaml @@ -242,6 +242,10 @@ templates: pathExpression: "'src/hooks/useSnacks.ts'" templateName: actor/src/hooks/useSnacks.ts.hbs + - name: actor/src/hooks/ViewDataContext.tsx + pathExpression: "'src/hooks/ViewDataContext.tsx'" + templateName: actor/src/hooks/ViewDataContext.tsx.hbs + # Actor - src - config - name: actor/src/config/index.ts