Skip to content

Commit

Permalink
Fixing frequent re-renders with latest app-lib-dotnet (#2683)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <git@olemartin.org>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Nov 5, 2024
1 parent c7a66b8 commit 41f43ad
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/features/datamodel/DataModelsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from 'src/features/datamodel/utils';
import { useLayouts } from 'src/features/form/layout/LayoutsContext';
import { useFormDataQuery } from 'src/features/formData/useFormDataQuery';
import { useLaxInstanceAllDataElements, useLaxInstanceDataElements } from 'src/features/instance/InstanceContext';
import { useLaxInstanceAllDataElementsNow, useLaxInstanceDataElements } from 'src/features/instance/InstanceContext';
import { MissingRolesError } from 'src/features/instantiate/containers/MissingRolesError';
import { useIsPdf } from 'src/hooks/useIsPdf';
import { isAxiosError } from 'src/utils/isAxiosError';
Expand Down Expand Up @@ -136,7 +136,7 @@ function DataModelsLoader() {
const layouts = useLayouts();
const defaultDataType = useCurrentDataModelName();
const isStateless = useApplicationMetadata().isStatelessApp;
const dataElements = useLaxInstanceAllDataElements();
const dataElements = useLaxInstanceAllDataElementsNow();

// Subform
const overriddenDataElement = useTaskStore((state) => state.overriddenDataModelUuid);
Expand Down
16 changes: 10 additions & 6 deletions src/features/datamodel/useBindingSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from 'src/features/applicationMetadata/appMetadataUtils';
import { DataModels } from 'src/features/datamodel/DataModelsProvider';
import { useLayoutSets } from 'src/features/form/layoutSets/LayoutSetsProvider';
import { useLaxInstanceAllDataElements, useLaxInstanceId } from 'src/features/instance/InstanceContext';
import { useLaxInstanceData, useLaxInstanceId } from 'src/features/instance/InstanceContext';
import { useProcessTaskId } from 'src/features/instance/useProcessTaskId';
import { useCurrentLanguage } from 'src/features/language/LanguageProvider';
import { useAllowAnonymous } from 'src/features/stateless/getAllowAnonymous';
Expand All @@ -29,17 +29,21 @@ export type AsSchema<T> = {
};

export function useCurrentDataModelGuid() {
const dataElements = useLaxInstanceAllDataElements();
const application = useApplicationMetadata();
const layoutSets = useLayoutSets();
const taskId = useProcessTaskId();

const overriddenDataModelGuid = useTaskStore((s) => s.overriddenDataModelUuid);
if (overriddenDataModelGuid) {
return overriddenDataModelGuid;
}

return getCurrentTaskDataElementId({ application, dataElements, taskId, layoutSets });
// Instance data elements will update often (after each save), so we have to use a selector to make
// sure components don't re-render too often.
return useLaxInstanceData((data) => {
if (overriddenDataModelGuid) {
return overriddenDataModelGuid;
}

return getCurrentTaskDataElementId({ application, dataElements: data.data, taskId, layoutSets });
});
}

type DataModelDeps = {
Expand Down
33 changes: 30 additions & 3 deletions src/features/instance/InstanceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ export type ChangeInstanceData = (callback: (instance: IInstance | undefined) =>

type InstanceStoreProps = Pick<InstanceContext, 'partyId' | 'instanceGuid'>;

const { Provider, useMemoSelector, useSelector, useLaxMemoSelector, useHasProvider } = createZustandContext({
const {
Provider,
useMemoSelector,
useSelector,
useLaxMemoSelector,
useHasProvider,
useLaxStore,
useLaxDelayedSelector,
} = createZustandContext({
name: 'InstanceContext',
required: true,
initialCreateStore: (props: InstanceStoreProps) =>
Expand Down Expand Up @@ -219,8 +227,6 @@ export const useLaxInstanceId = () => useLaxInstance((state) => state.instanceId
export const useLaxInstanceData = <U,>(selector: (data: IInstance) => U) =>
useLaxInstance((state) => (state.data ? selector(state.data) : undefined));
export const useLaxInstanceAllDataElements = () => useLaxInstance((state) => state.data?.data) ?? emptyArray;
export const useLaxInstanceDataElements = (dataType: string | undefined) =>
useLaxInstance((state) => state.data?.data.filter((d) => d.dataType === dataType)) ?? emptyArray;
export const useLaxInstanceStatus = () => useLaxInstance((state) => state.data?.status);
export const useLaxAppendDataElement = () => useLaxInstance((state) => state.appendDataElement);
export const useLaxMutateDataElement = () => useLaxInstance((state) => state.mutateDataElement);
Expand All @@ -229,6 +235,27 @@ export const useLaxInstanceDataSources = () => useLaxInstance((state) => state.d
export const useLaxChangeInstance = (): ChangeInstanceData | undefined => useLaxInstance((state) => state.changeData);
export const useHasInstance = () => useHasProvider();

/** Beware that in later versions, this will re-render your component after every save, as
* the backend sends us updated instance data */
export const useLaxInstanceDataElements = (dataType: string | undefined) =>
useLaxInstance((state) => state.data?.data.filter((d) => d.dataType === dataType)) ?? emptyArray;

export type DataElementSelector = <U>(selector: (data: IData[]) => U, deps: unknown[]) => U | typeof ContextNotProvided;
export const useLaxDataElementsSelector = (): DataElementSelector =>
useLaxDelayedSelector({
mode: 'innerSelector',
makeArgs: (state) => [state.data?.data ?? emptyArray],
});

/** Like useLaxInstanceAllDataElements, but will never re-render when the data changes */
export const useLaxInstanceAllDataElementsNow = () => {
const store = useLaxStore();
if (store === ContextNotProvided) {
return emptyArray;
}
return store.getState().data?.data ?? emptyArray;
};

export const useStrictInstanceRefetch = () => useSelector((state) => state.reFetch);
export const useStrictInstanceId = () => useSelector((state) => state.instanceId);
export const useStrictAppendDataElement = () => useSelector((state) => state.appendDataElement);
Expand Down
4 changes: 2 additions & 2 deletions src/features/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ApplicationMetadata } from 'src/features/applicationMetadata/types';
import type { AttachmentsSelector } from 'src/features/attachments/AttachmentsStorePlugin';
import type { Expression, ExprValToActual } from 'src/features/expressions/types';
import type { DataElementSelector } from 'src/features/instance/InstanceContext';
import type { TextReference, ValidLangParam } from 'src/features/language/useLanguage';
import type { DataElementHasErrorsSelector } from 'src/features/validation/validationContext';
import type { FormDataSelector } from 'src/layout';
import type { ILayoutSets } from 'src/layout/common.generated';
import type { IData } from 'src/types/shared';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';
import type { NodeDataSelector } from 'src/utils/layout/NodesContext';

Expand Down Expand Up @@ -225,7 +225,7 @@ export type ValidationDataSources = {
attachmentsSelector: AttachmentsSelector;
nodeDataSelector: NodeDataSelector;
applicationMetadata: ApplicationMetadata;
dataElements: IData[];
dataElementsSelector: DataElementSelector;
layoutSets: ILayoutSets;
dataElementHasErrorsSelector: DataElementHasErrorsSelector;
};
Expand Down
8 changes: 4 additions & 4 deletions src/features/validation/nodeValidation/useNodeValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useAttachmentsSelector } from 'src/features/attachments/hooks';
import { DataModels } from 'src/features/datamodel/DataModelsProvider';
import { useLayoutSets } from 'src/features/form/layoutSets/LayoutSetsProvider';
import { FD } from 'src/features/formData/FormDataWrite';
import { useLaxInstanceAllDataElements } from 'src/features/instance/InstanceContext';
import { useLaxDataElementsSelector } from 'src/features/instance/InstanceContext';
import { useCurrentLanguage } from 'src/features/language/LanguageProvider';
import { Validation } from 'src/features/validation/validationContext';
import { implementsValidateComponent, implementsValidateEmptyField } from 'src/layout';
Expand Down Expand Up @@ -85,7 +85,7 @@ function useValidationDataSources(): ValidationDataSources {
const currentLanguage = useCurrentLanguage();
const nodeSelector = NodesInternal.useNodeDataSelector();
const applicationMetadata = useApplicationMetadata();
const dataElements = useLaxInstanceAllDataElements();
const dataElementsSelector = useLaxDataElementsSelector();
const layoutSets = useLayoutSets();
const dataElementHasErrorsSelector = Validation.useDataElementHasErrorsSelector();

Expand All @@ -97,7 +97,7 @@ function useValidationDataSources(): ValidationDataSources {
currentLanguage,
nodeDataSelector: nodeSelector,
applicationMetadata,
dataElements,
dataElementsSelector,
layoutSets,
dataElementHasErrorsSelector,
}),
Expand All @@ -108,7 +108,7 @@ function useValidationDataSources(): ValidationDataSources {
currentLanguage,
nodeSelector,
applicationMetadata,
dataElements,
dataElementsSelector,
layoutSets,
dataElementHasErrorsSelector,
],
Expand Down
10 changes: 5 additions & 5 deletions src/layout/AttachmentList/AttachmentListComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useMemo } from 'react';
import React from 'react';

import { AltinnAttachment } from 'src/components/atoms/AltinnAttachment';
import { useApplicationMetadata } from 'src/features/applicationMetadata/ApplicationMetadataProvider';
import { useLaxInstanceAllDataElements } from 'src/features/instance/InstanceContext';
import { useLaxInstanceData } from 'src/features/instance/InstanceContext';
import { useLaxProcessData } from 'src/features/instance/ProcessContext';
import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper';
import { DataTypeReference, filterDisplayPdfAttachments, getDisplayAttachments } from 'src/utils/attachmentsUtils';
Expand All @@ -15,12 +15,12 @@ export type IAttachmentListProps = PropsFromGenericComponent<'AttachmentList'>;
const emptyDataTypeArray: IDataType[] = [];

export function AttachmentListComponent({ node }: IAttachmentListProps) {
const instanceData = useLaxInstanceAllDataElements();
const currentTaskId = useLaxProcessData()?.currentTask?.elementId;
const dataTypes = useApplicationMetadata().dataTypes ?? emptyDataTypeArray;
const { dataTypeIds, textResourceBindings } = useNodeItem(node);

const attachments = useMemo(() => {
const attachments = useLaxInstanceData((data) => {
const instanceData = data.data ?? [];
const allowedTypes = new Set(dataTypeIds ?? []);
const includePdf =
allowedTypes.has(DataTypeReference.RefDataAsPdf) || allowedTypes.has(DataTypeReference.IncludeAll);
Expand Down Expand Up @@ -54,7 +54,7 @@ export function AttachmentListComponent({ node }: IAttachmentListProps) {

const otherAttachments = getDisplayAttachments(attachments);
return [...pdfAttachments, ...otherAttachments];
}, [currentTaskId, dataTypes, instanceData, dataTypeIds]);
});

return (
<ComponentStructureWrapper node={node}>
Expand Down
10 changes: 6 additions & 4 deletions src/layout/Subform/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Subform extends SubformDef implements ValidateComponent<'Subform'>,
node: LayoutNode<'Subform'>,
{
applicationMetadata,
dataElements,
dataElementsSelector,
nodeDataSelector,
layoutSets,
dataElementHasErrorsSelector,
Expand All @@ -107,8 +107,8 @@ export class Subform extends SubformDef implements ValidateComponent<'Subform'>,

const validations: ComponentValidation[] = [];

const elements = dataElements.filter((x) => x.dataType === targetType);
const numDataElements = elements?.length ?? 0;
const elements = dataElementsSelector((d) => d.filter((x) => x.dataType === targetType), [targetType]);
const numDataElements = Array.isArray(elements) ? elements.length : 0;
const { minCount, maxCount } = dataTypeDefinition;

if (minCount > 0 && numDataElements < minCount) {
Expand All @@ -129,7 +129,9 @@ export class Subform extends SubformDef implements ValidateComponent<'Subform'>,
});
}

const subformIdsWithError = elements?.map((dE) => dE.id).filter((id) => dataElementHasErrorsSelector(id));
const subformIdsWithError = Array.isArray(elements)
? elements?.map((dE) => dE.id).filter((id) => dataElementHasErrorsSelector(id))
: [];
if (subformIdsWithError?.length) {
const validation: SubformValidation = {
subformDataElementIds: subformIdsWithError,
Expand Down

0 comments on commit 41f43ad

Please sign in to comment.