Skip to content

Commit

Permalink
Rewriting AppRoutingContext to avoid late URL state problems (#2707)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <[email protected]>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Nov 13, 2024
1 parent 971a246 commit 12cca18
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 125 deletions.
10 changes: 5 additions & 5 deletions src/components/form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { useLocation } from 'react-router-dom';

import Grid from '@material-ui/core/Grid';
import deepEqual from 'fast-deep-equal';
Expand All @@ -18,15 +17,17 @@ import { useUiConfigContext } from 'src/features/form/layout/UiConfigContext';
import { usePageSettings } from 'src/features/form/layoutSettings/LayoutSettingsContext';
import { useLanguage } from 'src/features/language/useLanguage';
import {
SearchParams,
useNavigate,
useNavigationParam,
useNavigationPath,
useQueryKey,
useQueryKeysAsString,
useQueryKeysAsStringAsRef,
} from 'src/features/routing/AppRoutingContext';
import { useOnFormSubmitValidation } from 'src/features/validation/callbacks/onFormSubmitValidation';
import { useTaskErrors } from 'src/features/validation/selectors/taskErrors';
import { SearchParams, useCurrentView, useNavigatePage, useStartUrl } from 'src/hooks/useNavigatePage';
import { useCurrentView, useNavigatePage, useStartUrl } from 'src/hooks/useNavigatePage';
import { GenericComponentById } from 'src/layout/GenericComponent';
import { extractBottomButtons } from 'src/utils/formLayout';
import { getPageTitle } from 'src/utils/getPageTitle';
Expand Down Expand Up @@ -159,7 +160,7 @@ export function FormFirstPage() {
const navigate = useNavigate();
const startUrl = useStartUrl();

const currentLocation = `${useLocation().pathname}${useQueryKeysAsString()}`;
const currentLocation = `${useNavigationPath()}${useQueryKeysAsString()}`;

useEffect(() => {
if (currentLocation !== startUrl) {
Expand All @@ -181,7 +182,6 @@ function useRedirectToStoredPage() {
const instanceGuid = useNavigationParam('instanceGuid');
const { isValidPageId, navigateToPage } = useNavigatePage();
const applicationMetadataId = useApplicationMetadata()?.id;
const location = useLocation().pathname;

const instanceId = `${partyId}/${instanceGuid}`;
const currentViewCacheKey = instanceId || applicationMetadataId;
Expand All @@ -194,7 +194,7 @@ function useRedirectToStoredPage() {
navigateToPage(lastVisitedPage, { replace: true });
}
}
}, [pageKey, currentViewCacheKey, isValidPageId, location, navigateToPage]);
}, [pageKey, currentViewCacheKey, isValidPageId, navigateToPage]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/form/LinkToPotentialNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Link } from 'react-router-dom';
import type { LinkProps } from 'react-router-dom';

import { SearchParams } from 'src/hooks/useNavigatePage';
import { SearchParams } from 'src/features/routing/AppRoutingContext';
import { Hidden, useNode } from 'src/utils/layout/NodesContext';

type Props = LinkProps & { children?: React.ReactNode };
Expand Down
11 changes: 8 additions & 3 deletions src/components/wrappers/ProcessWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
import { Route, Routes, useLocation } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom';

import { Button } from '@digdir/designsystemet-react';
import Grid from '@material-ui/core/Grid';
Expand All @@ -21,7 +21,12 @@ import { PDFWrapper } from 'src/features/pdf/PDFWrapper';
import { Confirm } from 'src/features/processEnd/confirm/containers/Confirm';
import { Feedback } from 'src/features/processEnd/feedback/Feedback';
import { ReceiptContainer } from 'src/features/receipt/ReceiptContainer';
import { useNavigate, useNavigationParam, useQueryKeysAsString } from 'src/features/routing/AppRoutingContext';
import {
useNavigate,
useNavigationParam,
useNavigationPath,
useQueryKeysAsString,
} from 'src/features/routing/AppRoutingContext';
import { TaskKeys, useIsCurrentTask, useNavigatePage, useStartUrl } from 'src/hooks/useNavigatePage';
import { implementsSubRouting } from 'src/layout';
import { RedirectBackToMainForm } from 'src/layout/Subform/SubformWrapper';
Expand Down Expand Up @@ -86,7 +91,7 @@ export function NavigateToStartUrl() {
const currentTaskId = useLaxProcessData()?.currentTask?.elementId;
const startUrl = useStartUrl(currentTaskId);

const currentLocation = `${useLocation().pathname}${useQueryKeysAsString()}`;
const currentLocation = `${useNavigationPath()}${useQueryKeysAsString()}`;

useEffect(() => {
if (currentLocation !== startUrl) {
Expand Down
31 changes: 23 additions & 8 deletions src/features/datamodel/DataModelsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
MissingDataTypeException,
} from 'src/features/datamodel/utils';
import { useLayouts } from 'src/features/form/layout/LayoutsContext';
import { useCurrentLayoutSetId } from 'src/features/form/layoutSets/useCurrentLayoutSet';
import { useFormDataQuery } from 'src/features/formData/useFormDataQuery';
import { useLaxInstanceAllDataElementsNow, useLaxInstanceDataElements } from 'src/features/instance/InstanceContext';
import { MissingRolesError } from 'src/features/instantiate/containers/MissingRolesError';
Expand All @@ -33,6 +34,7 @@ import type { IExpressionValidations } from 'src/features/validation';
import type { IDataModelReference } from 'src/layout/common.generated';

interface DataModelsState {
layoutSetId: string | undefined;
defaultDataType: string | undefined;
allDataTypes: string[] | null;
writableDataTypes: string[] | null;
Expand All @@ -45,7 +47,12 @@ interface DataModelsState {
}

interface DataModelsMethods {
setDataTypes: (allDataTypes: string[], writableDataTypes: string[], defaultDataType: string | undefined) => void;
setDataTypes: (
allDataTypes: string[],
writableDataTypes: string[],
defaultDataType: string | undefined,
layoutSetId: string | undefined,
) => void;
setInitialData: (dataType: string, initialData: object, dataElementId: string | null) => void;
setDataModelSchema: (dataType: string, schema: JSONSchema7, lookupTool: SchemaLookupTool) => void;
setExpressionValidationConfig: (dataType: string, config: IExpressionValidations | null) => void;
Expand All @@ -54,6 +61,7 @@ interface DataModelsMethods {

function initialCreateStore() {
return createStore<DataModelsState & DataModelsMethods>()((set) => ({
layoutSetId: undefined,
defaultDataType: undefined,
allDataTypes: null,
writableDataTypes: null,
Expand All @@ -65,8 +73,8 @@ function initialCreateStore() {
expressionValidationConfigs: {},
error: null,

setDataTypes: (allDataTypes, writableDataTypes, defaultDataType) => {
set(() => ({ allDataTypes, writableDataTypes, defaultDataType }));
setDataTypes: (allDataTypes, writableDataTypes, defaultDataType, layoutSetId) => {
set(() => ({ allDataTypes, writableDataTypes, defaultDataType, layoutSetId }));
},
setInitialData: (dataType, initialData, dataElementId) => {
set((state) => ({
Expand Down Expand Up @@ -137,6 +145,7 @@ function DataModelsLoader() {
const defaultDataType = useCurrentDataModelName();
const isStateless = useApplicationMetadata().isStatelessApp;
const dataElements = useLaxInstanceAllDataElementsNow();
const layoutSetId = useCurrentLayoutSetId();

// Subform
const overriddenDataElement = useTaskStore((state) => state.overriddenDataModelUuid);
Expand Down Expand Up @@ -175,8 +184,8 @@ function DataModelsLoader() {
}
}

setDataTypes(allValidDataTypes, writableDataTypes, defaultDataType);
}, [applicationMetadata, defaultDataType, isStateless, layouts, setDataTypes, dataElements]);
setDataTypes(allValidDataTypes, writableDataTypes, defaultDataType, layoutSetId);
}, [applicationMetadata, defaultDataType, isStateless, layouts, setDataTypes, dataElements, layoutSetId]);

// We should load form data and schema for all referenced data models, schema is used for dataModelBinding validation which we want to do even if it is readonly
// We only need to load expression validation config for data types that are not readonly. Additionally, backend will error if we try to validate a model we are not supposed to
Expand All @@ -201,9 +210,9 @@ function DataModelsLoader() {
}

function BlockUntilLoaded({ children }: PropsWithChildren) {
const { allDataTypes, writableDataTypes, initialData, schemas, expressionValidationConfigs, error } = useSelector(
(state) => state,
);
const { layoutSetId, allDataTypes, writableDataTypes, initialData, schemas, expressionValidationConfigs, error } =
useSelector((state) => state);
const actualCurrentTask = useCurrentLayoutSetId();
const isPDF = useIsPdf();

if (error) {
Expand All @@ -219,6 +228,12 @@ function BlockUntilLoaded({ children }: PropsWithChildren) {
return <Loader reason='data-types' />;
}

if (layoutSetId !== actualCurrentTask) {
// The layout-set has changed since the state was set, so we need to wait for the new layout-set to be loaded
// and the relevant data model bindings there to be parsed.
return <Loader reason='layout-set-change' />;
}

// in PDF mode, we do not load schema, or expression validation config. So we should not block loading in that case
// Edit: Since #2244, layout and data model binding validations work differently, so enabling schema loading to make things work for now.

Expand Down
2 changes: 1 addition & 1 deletion src/features/expressions/expression-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Mutable } from 'utility-types';
import { ExprRuntimeError, NodeNotFound, NodeNotFoundWithoutContext } from 'src/features/expressions/errors';
import { ExprVal } from 'src/features/expressions/types';
import { addError } from 'src/features/expressions/validation';
import { SearchParams } from 'src/hooks/useNavigatePage';
import { SearchParams } from 'src/features/routing/AppRoutingContext';
import { implementsDisplayData } from 'src/layout';
import { buildAuthContext } from 'src/utils/authContext';
import { isDate } from 'src/utils/dateHelpers';
Expand Down
4 changes: 2 additions & 2 deletions src/features/instance/ProcessNavigationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useLaxInstanceId, useStrictInstanceRefetch } from 'src/features/instanc
import { useReFetchProcessData } from 'src/features/instance/ProcessContext';
import { Lang } from 'src/features/language/Lang';
import { useCurrentLanguage } from 'src/features/language/LanguageProvider';
import { useNavigationParam } from 'src/features/routing/AppRoutingContext';
import { useIsSubformPage } from 'src/features/routing/AppRoutingContext';
import { useUpdateInitialValidations } from 'src/features/validation/backendValidation/backendValidationQuery';
import { appSupportsIncrementalValidationFeatures } from 'src/features/validation/backendValidation/backendValidationUtils';
import { useOnFormSubmitValidation } from 'src/features/validation/callbacks/onFormSubmitValidation';
Expand Down Expand Up @@ -174,7 +174,7 @@ export function ProcessNavigationProvider({ children }: React.PropsWithChildren)

export const useProcessNavigation = () => {
// const { isSubformPage } = useNavigationParams();
const isSubformPage = useNavigationParam('isSubformPage');
const isSubformPage = useIsSubformPage();
if (isSubformPage) {
throw new Error('Cannot use process navigation in a subform');
}
Expand Down
Loading

0 comments on commit 12cca18

Please sign in to comment.