Skip to content

Commit

Permalink
fix(translations): memoize array translation
Browse files Browse the repository at this point in the history
This commit addresses an issue where array translations were created as new objects within the core module for each render
cycle, causing unnecessary rerenders. By memoizing the translation object in the material renderer set, this commit prevents redundant rerenders.
This commit also adds memorization to the attributes of the material oneOfRenderer to further avoid unnecessary rerendering.

closes #2342
  • Loading branch information
LukasBoll committed Aug 15, 2024
1 parent 26fd0e9 commit 6f275fb
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ import {
JsonFormsAbstractControl,
} from '@jsonforms/angular';
import {
arrayDefaultTranslations,
ArrayLayoutProps,
ArrayTranslations,
createDefaultValue,
defaultJsonFormsI18nState,
findUISchema,
getArrayTranslations,
isObjectArrayWithNesting,
JsonFormsState,
mapDispatchToArrayControlProps,
Expand Down Expand Up @@ -169,7 +172,7 @@ export class ArrayLayoutRenderer
implements OnInit, OnDestroy
{
noData: boolean;
translations: ArrayTranslations;
translations: ArrayTranslations = {};
addItem: (path: string, value: any) => () => void;
moveItemUp: (path: string, index: number) => () => void;
moveItemDown: (path: string, index: number) => () => void;
Expand All @@ -181,9 +184,19 @@ export class ArrayLayoutRenderer
constructor(jsonFormsService: JsonFormsAngularService) {
super(jsonFormsService);
}
mapToProps(state: JsonFormsState): StatePropsOfArrayLayout {
mapToProps(
state: JsonFormsState
): StatePropsOfArrayLayout & { translations: ArrayTranslations } {
const props = mapStateToArrayLayoutProps(state, this.getOwnProps());
return { ...props };
const t =
state.jsonforms.i18n?.translate ?? defaultJsonFormsI18nState.translate;
const translations = getArrayTranslations(
t,
arrayDefaultTranslations,
props.i18nKeyPrefix,
props.label
);
return { ...props, translations };
}
remove(index: number): void {
this.removeItems(this.propsPath, [index])();
Expand Down Expand Up @@ -211,10 +224,12 @@ export class ArrayLayoutRenderer
this.moveItemDown = moveDown;
this.removeItems = removeItems;
}
mapAdditionalProps(props: ArrayLayoutProps) {
this.translations = props.translations;
mapAdditionalProps(
props: ArrayLayoutProps & { translations: ArrayTranslations }
) {
this.noData = !props.data || props.data === 0;
this.uischemas = props.uischemas;
this.translations = props.translations;
}
getProps(index: number): OwnPropsOfRenderer {
const uischema = findUISchema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ import {
} from '@jsonforms/angular';
import {
ArrayControlProps,
arrayDefaultTranslations,
ArrayTranslations,
ControlElement,
createDefaultValue,
decode,
defaultJsonFormsI18nState,
findUISchema,
getArrayTranslations,
getFirstPrimitiveProp,
JsonFormsState,
mapDispatchToArrayControlProps,
Expand Down Expand Up @@ -194,7 +197,9 @@ export class MasterListComponent
this.removeItems = removeItems;
}

mapAdditionalProps(props: ArrayControlProps) {
mapAdditionalProps(
props: ArrayControlProps & { translations: ArrayTranslations }
) {
const { data, path, schema, uischema } = props;
const controlElement = uischema as ControlElement;
this.propsPath = props.path;
Expand Down Expand Up @@ -282,9 +287,19 @@ export class MasterListComponent
this.removeItems(this.propsPath, [item])();
}

protected mapToProps(state: JsonFormsState): StatePropsOfArrayControl {
protected mapToProps(
state: JsonFormsState
): StatePropsOfArrayControl & { translations: ArrayTranslations } {
const props = mapStateToArrayControlProps(state, this.getOwnProps());
return { ...props };
const t =
state.jsonforms.i18n?.translate ?? defaultJsonFormsI18nState.translate;
const translations = getArrayTranslations(
t,
arrayDefaultTranslations,
props.i18nKeyPrefix,
props.label
);
return { ...props, translations };
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/angular-material/src/library/other/table.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,17 @@ export class TableRenderer extends JsonFormsArrayControl implements OnInit {
moveItemUp: (path: string, index: number) => () => void;
moveItemDown: (path: string, index: number) => () => void;
removeItems: (path: string, toDelete: number[]) => () => void;
translations: ArrayTranslations;
translations: ArrayTranslations = {};

constructor(jsonformsService: JsonFormsAngularService) {
super(jsonformsService);
}
trackElement(index: number, _element: any) {
return index ? index : null;
}
mapAdditionalProps(props: ArrayControlProps) {
mapAdditionalProps(
props: ArrayControlProps & { translations: ArrayTranslations }
) {
this.items = this.generateCells(props.schema, props.path);
this.displayedColumns = this.items.map((item) => item.property);
if (this.isEnabled()) {
Expand Down
18 changes: 16 additions & 2 deletions packages/angular/src/library/array-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
THE SOFTWARE.
*/
import {
arrayDefaultTranslations,
ArrayTranslations,
defaultJsonFormsI18nState,
getArrayTranslations,
JsonFormsState,
mapStateToArrayControlProps,
StatePropsOfArrayControl,
Expand All @@ -34,8 +38,18 @@ export class JsonFormsArrayControl
extends JsonFormsAbstractControl<StatePropsOfArrayControl>
implements OnInit, OnDestroy
{
protected mapToProps(state: JsonFormsState): StatePropsOfArrayControl {
protected mapToProps(
state: JsonFormsState
): StatePropsOfArrayControl & { translations: ArrayTranslations } {
const props = mapStateToArrayControlProps(state, this.getOwnProps());
return { ...props };
const t =
state.jsonforms.i18n?.translate ?? defaultJsonFormsI18nState.translate;
const translations = getArrayTranslations(
t,
arrayDefaultTranslations,
props.i18nKeyPrefix,
props.label
);
return { ...props, translations };
}
}
33 changes: 2 additions & 31 deletions packages/core/src/mappers/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,8 @@ import {
getI18nKey,
getI18nKeyPrefix,
getI18nKeyPrefixBySchema,
getArrayTranslations,
CombinatorTranslations,
getCombinatorTranslations,
combinatorDefaultTranslations,
getTranslator,
getErrorTranslator,
arrayDefaultTranslations,
ArrayTranslations,
} from '../i18n';
import cloneDeep from 'lodash/cloneDeep';
Expand Down Expand Up @@ -789,7 +784,6 @@ export interface ControlWithDetailProps
*/
export interface StatePropsOfArrayControl
extends StatePropsOfControlWithDetail {
translations: ArrayTranslations;
childErrors?: ErrorObject[];
}

Expand All @@ -804,12 +798,11 @@ export const mapStateToArrayControlProps = (
state: JsonFormsState,
ownProps: OwnPropsOfControl
): StatePropsOfArrayControl => {
const { path, schema, uischema, i18nKeyPrefix, label, ...props } =
const { path, schema, uischema, label, ...props } =
mapStateToControlWithDetailProps(state, ownProps);

const resolvedSchema = Resolve.schema(schema, 'items', props.rootSchema);
const childErrors = getSubErrorsAt(path, resolvedSchema)(state);
const t = getTranslator()(state);

return {
...props,
Expand All @@ -820,12 +813,6 @@ export const mapStateToArrayControlProps = (
childErrors,
renderers: ownProps.renderers || getRenderers(state),
cells: ownProps.cells || getCells(state),
translations: getArrayTranslations(
t,
arrayDefaultTranslations,
i18nKeyPrefix,
label
),
};
};

Expand Down Expand Up @@ -1060,7 +1047,6 @@ export interface StatePropsOfCombinator extends StatePropsOfControl {
indexOfFittingSchema: number;
uischemas: JsonFormsUISchemaRegistryEntry[];
data: any;
translations: CombinatorTranslations;
}

export const mapStateToCombinatorRendererProps = (
Expand All @@ -1072,13 +1058,6 @@ export const mapStateToCombinatorRendererProps = (
mapStateToControlProps(state, ownProps);

const ajv = state.jsonforms.core.ajv;
const t = getTranslator()(state);
const translations = getCombinatorTranslations(
t,
combinatorDefaultTranslations,
i18nKeyPrefix,
label
);
const structuralKeywords = [
'required',
'additionalProperties',
Expand Down Expand Up @@ -1125,7 +1104,6 @@ export const mapStateToCombinatorRendererProps = (
label,
indexOfFittingSchema,
uischemas: getUISchemas(state),
translations,
};
};

Expand Down Expand Up @@ -1160,7 +1138,6 @@ export const mapStateToOneOfProps = (

export interface StatePropsOfArrayLayout extends StatePropsOfControlWithDetail {
data: number;
translations: ArrayTranslations;
minItems?: number;
disableRemove?: boolean;
disableAdd?: boolean;
Expand All @@ -1176,7 +1153,7 @@ export const mapStateToArrayLayoutProps = (
state: JsonFormsState,
ownProps: OwnPropsOfControl
): StatePropsOfArrayLayout => {
const { path, schema, uischema, errors, i18nKeyPrefix, label, ...props } =
const { path, schema, uischema, errors, label, ...props } =
mapStateToControlWithDetailProps(state, ownProps);

const resolvedSchema = Resolve.schema(schema, 'items', props.rootSchema);
Expand Down Expand Up @@ -1204,12 +1181,6 @@ export const mapStateToArrayLayoutProps = (
data: props.data ? props.data.length : 0,
errors: allErrors,
minItems: schema.minItems,
translations: getArrayTranslations(
t,
arrayDefaultTranslations,
i18nKeyPrefix,
label
),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import {
and,
ArrayLayoutProps,
ArrayTranslations,
composePaths,
computeLabel,
createDefaultValue,
Expand All @@ -36,7 +37,9 @@ import {
} from '@jsonforms/core';
import {
JsonFormsDispatch,
withArrayTranslationProps,
withJsonFormsArrayLayoutProps,
withTranslateProps,
} from '@jsonforms/react';
import { Grid, List, Typography } from '@mui/material';
import map from 'lodash/map';
Expand All @@ -63,11 +66,11 @@ export const MaterialListWithDetailRenderer = ({
cells,
config,
rootSchema,
translations,
description,
disableAdd,
disableRemove,
}: ArrayLayoutProps) => {
translations,
}: ArrayLayoutProps & { translations: ArrayTranslations }) => {
const [selectedIndex, setSelectedIndex] = useState(undefined);
const handleRemoveItem = useCallback(
(p: string, value: any) => () => {
Expand Down Expand Up @@ -101,6 +104,7 @@ export const MaterialListWithDetailRenderer = ({
),
[uischemas, schema, uischema.scope, path, uischema, rootSchema]
);

const appliedUiSchemaOptions = merge({}, config, uischema.options);
const doDisableAdd = disableAdd || appliedUiSchemaOptions.disableAdd;
const doDisableRemove = disableRemove || appliedUiSchemaOptions.disableRemove;
Expand Down Expand Up @@ -179,4 +183,6 @@ export const materialListWithDetailTester: RankedTester = rankWith(
and(uiTypeIs('ListWithDetail'), isObjectArray)
);

export default withJsonFormsArrayLayoutProps(MaterialListWithDetailRenderer);
export default withJsonFormsArrayLayoutProps(
withTranslateProps(withArrayTranslationProps(MaterialListWithDetailRenderer))
);
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,28 @@
import React, { useCallback, useState } from 'react';
import {
ArrayLayoutProps,
ArrayTranslations,
RankedTester,
isObjectArrayControl,
isPrimitiveArrayControl,
or,
rankWith,
} from '@jsonforms/core';
import { withJsonFormsArrayLayoutProps } from '@jsonforms/react';
import {
withArrayTranslationProps,
withJsonFormsArrayLayoutProps,
withTranslateProps,
} from '@jsonforms/react';
import { MaterialTableControl } from './MaterialTableControl';
import { DeleteDialog } from './DeleteDialog';

export const MaterialArrayControlRenderer = (props: ArrayLayoutProps) => {
export const MaterialArrayControlRenderer = (
props: ArrayLayoutProps & { translations: ArrayTranslations }
) => {
const [open, setOpen] = useState(false);
const [path, setPath] = useState(undefined);
const [rowData, setRowData] = useState(undefined);
const { removeItems, visible } = props;
const { removeItems, visible, translations } = props;

const openDeleteDialog = useCallback(
(p: string, rowIndex: number) => {
Expand All @@ -63,16 +70,20 @@ export const MaterialArrayControlRenderer = (props: ArrayLayoutProps) => {

return (
<>
<MaterialTableControl {...props} openDeleteDialog={openDeleteDialog} />
<MaterialTableControl
{...props}
openDeleteDialog={openDeleteDialog}
translations={translations}
/>
<DeleteDialog
open={open}
onCancel={deleteCancel}
onConfirm={deleteConfirm}
onClose={deleteClose}
acceptText={props.translations.deleteDialogAccept}
declineText={props.translations.deleteDialogDecline}
title={props.translations.deleteDialogTitle}
message={props.translations.deleteDialogMessage}
acceptText={translations.deleteDialogAccept}
declineText={translations.deleteDialogDecline}
title={translations.deleteDialogTitle}
message={translations.deleteDialogMessage}
/>
</>
);
Expand All @@ -83,4 +94,6 @@ export const materialArrayControlTester: RankedTester = rankWith(
or(isObjectArrayControl, isPrimitiveArrayControl)
);

export default withJsonFormsArrayLayoutProps(MaterialArrayControlRenderer);
export default withJsonFormsArrayLayoutProps(
withTranslateProps(withArrayTranslationProps(MaterialArrayControlRenderer))
);
Loading

0 comments on commit 6f275fb

Please sign in to comment.