Skip to content

Commit a646e83

Browse files
authored
fix: make sure React Vanilla category rendering is up to date
Previously the active categorization was placed in state. When the UI Schema changed on the fly it was possible that the renderer continued rendering the outdated category. This is now fixed. Also refactors the renderer from class component to a functional component and adds category filtering logic.
1 parent a4ff972 commit a646e83

File tree

7 files changed

+339
-191
lines changed

7 files changed

+339
-191
lines changed

packages/vanilla-renderers/src/complex/categorization/CategorizationList.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,55 +24,65 @@
2424
*/
2525
import React, { useMemo } from 'react';
2626
import {
27-
Categorization,
2827
Category,
28+
Categorization,
2929
deriveLabelForUISchemaElement,
3030
Translator,
31+
isVisible,
3132
} from '@jsonforms/core';
3233
import { isCategorization } from './tester';
34+
import { AjvProps } from '../../util';
3335

3436
const getCategoryClassName = (
3537
category: Category,
3638
selectedCategory: Category
3739
): string => (selectedCategory === category ? 'selected' : '');
3840

3941
export interface CategorizationProps {
40-
categorization: Categorization;
42+
elements: (Category | Categorization)[];
4143
selectedCategory: Category;
4244
depth: number;
45+
data: any;
4346
onSelect: any;
4447
subcategoriesClassName: string;
4548
groupClassName: string;
4649
t: Translator;
4750
}
4851

4952
export const CategorizationList = ({
50-
categorization,
5153
selectedCategory,
54+
elements,
55+
data,
5256
depth,
5357
onSelect,
5458
subcategoriesClassName,
5559
groupClassName,
5660
t,
57-
}: CategorizationProps) => {
61+
ajv,
62+
}: CategorizationProps & AjvProps) => {
63+
const filteredElements = useMemo(() => {
64+
return elements.filter((category: Category | Categorization) =>
65+
isVisible(category, data, undefined, ajv)
66+
);
67+
}, [elements, data, ajv]);
68+
5869
const categoryLabels = useMemo(
59-
() =>
60-
categorization.elements.map((cat) =>
61-
deriveLabelForUISchemaElement(cat, t)
62-
),
63-
[categorization, t]
70+
() => filteredElements.map((cat) => deriveLabelForUISchemaElement(cat, t)),
71+
[filteredElements, t]
6472
);
6573

6674
return (
6775
<ul className={subcategoriesClassName}>
68-
{categorization.elements.map((category, idx) => {
76+
{filteredElements.map((category, idx) => {
6977
if (isCategorization(category)) {
7078
return (
7179
<li key={categoryLabels[idx]} className={groupClassName}>
7280
<span>{categoryLabels[idx]}</span>
7381
<CategorizationList
74-
categorization={category}
7582
selectedCategory={selectedCategory}
83+
elements={category.elements}
84+
data={data}
85+
ajv={ajv}
7686
depth={depth + 1}
7787
onSelect={onSelect}
7888
subcategoriesClassName={subcategoriesClassName}
@@ -85,7 +95,7 @@ export const CategorizationList = ({
8595
return (
8696
<li
8797
key={categoryLabels[idx]}
88-
onClick={onSelect(category)}
98+
onClick={onSelect(idx)}
8999
className={getCategoryClassName(category, selectedCategory)}
90100
>
91101
<span>{categoryLabels[idx]}</span>

packages/vanilla-renderers/src/complex/categorization/CategorizationRenderer.tsx

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -22,89 +22,102 @@
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
THE SOFTWARE.
2424
*/
25-
import React from 'react';
25+
import React, { useState } from 'react';
2626
import type { Categorization, Category, LayoutProps } from '@jsonforms/core';
2727
import {
28-
RendererComponent,
2928
TranslateProps,
3029
withJsonFormsLayoutProps,
3130
withTranslateProps,
3231
} from '@jsonforms/react';
3332
import { CategorizationList } from './CategorizationList';
3433
import { SingleCategory } from './SingleCategory';
35-
import { isCategorization } from './tester';
36-
import { withVanillaControlProps } from '../../util';
37-
import type { VanillaRendererProps } from '../../index';
34+
import { withAjvProps, withVanillaControlProps } from '../../util';
35+
import type { AjvProps, VanillaRendererProps } from '../../util';
3836

3937
export interface CategorizationState {
4038
selectedCategory: Category;
4139
}
4240

43-
class CategorizationRenderer extends RendererComponent<
44-
LayoutProps & VanillaRendererProps & TranslateProps,
45-
CategorizationState
46-
> {
47-
onCategorySelected = (category: Category) => () => {
48-
return this.setState({ selectedCategory: category });
49-
};
41+
interface CategorizationProps {
42+
selected?: number;
43+
onChange?(selected: number, prevSelected: number): void;
44+
}
5045

51-
/**
52-
* @inheritDoc
53-
*/
54-
render() {
55-
const { uischema, visible, getStyleAsClassName, t } = this.props;
56-
const categorization = uischema as Categorization;
57-
const classNames = getStyleAsClassName('categorization');
58-
const masterClassNames = getStyleAsClassName('categorization.master');
59-
const detailClassNames = getStyleAsClassName('categorization.detail');
60-
const selectedCategory = this.findCategory(categorization);
61-
const subcategoriesClassName = getStyleAsClassName(
62-
'category.subcategories'
63-
);
64-
const groupClassName = getStyleAsClassName('category.group');
46+
export const CategorizationRenderer = ({
47+
data,
48+
uischema,
49+
schema,
50+
path,
51+
selected,
52+
t,
53+
visible,
54+
getStyleAsClassName,
55+
onChange,
56+
ajv,
57+
}: LayoutProps &
58+
VanillaRendererProps &
59+
TranslateProps &
60+
CategorizationProps &
61+
AjvProps) => {
62+
const categorization = uischema as Categorization;
63+
const elements = categorization.elements as (Category | Categorization)[];
64+
const classNames = getStyleAsClassName('categorization');
65+
const masterClassNames = getStyleAsClassName('categorization.master');
66+
const detailClassNames = getStyleAsClassName('categorization.detail');
67+
const subcategoriesClassName = getStyleAsClassName('category.subcategories');
68+
const groupClassName = getStyleAsClassName('category.group');
6569

66-
return (
67-
<div
68-
className={classNames}
69-
hidden={visible === null || visible === undefined ? false : !visible}
70-
>
71-
<div className={masterClassNames}>
72-
<CategorizationList
73-
categorization={categorization}
74-
selectedCategory={selectedCategory}
75-
depth={0}
76-
onSelect={this.onCategorySelected}
77-
subcategoriesClassName={subcategoriesClassName}
78-
groupClassName={groupClassName}
79-
t={t}
80-
/>
81-
</div>
82-
<div className={detailClassNames}>
83-
<SingleCategory
84-
category={selectedCategory}
85-
schema={this.props.schema}
86-
path={this.props.path}
87-
/>
88-
</div>
89-
</div>
90-
);
91-
}
70+
const [previousCategorization, setPreviousCategorization] =
71+
useState<Categorization>(uischema as Categorization);
72+
const [activeCategory, setActiveCategory] = useState<number>(selected ?? 0);
9273

93-
private findCategory(categorization: Categorization): Category {
94-
const category = categorization.elements[0];
74+
const safeCategory =
75+
activeCategory >= categorization.elements.length ? 0 : activeCategory;
9576

96-
if (this.state && this.state.selectedCategory) {
97-
return this.state.selectedCategory;
98-
}
77+
if (categorization !== previousCategorization) {
78+
setActiveCategory(0);
79+
setPreviousCategorization(categorization);
80+
}
9981

100-
if (isCategorization(category)) {
101-
return this.findCategory(category);
82+
const onCategorySelected = (categoryIndex: number) => () => {
83+
if (onChange) {
84+
return onChange(categoryIndex, safeCategory);
10285
}
86+
return setActiveCategory(categoryIndex);
87+
};
10388

104-
return category;
105-
}
106-
}
89+
return (
90+
<div
91+
className={classNames}
92+
hidden={visible === null || visible === undefined ? false : !visible}
93+
>
94+
<div className={masterClassNames}>
95+
<CategorizationList
96+
elements={elements}
97+
selectedCategory={elements[safeCategory] as Category}
98+
data={data}
99+
ajv={ajv}
100+
depth={0}
101+
onSelect={onCategorySelected}
102+
subcategoriesClassName={subcategoriesClassName}
103+
groupClassName={groupClassName}
104+
t={t}
105+
/>
106+
</div>
107+
<div className={detailClassNames}>
108+
<SingleCategory
109+
category={elements[safeCategory] as Category}
110+
schema={schema}
111+
path={path}
112+
key={safeCategory}
113+
/>
114+
</div>
115+
</div>
116+
);
117+
};
107118

108-
export default withVanillaControlProps(
109-
withTranslateProps(withJsonFormsLayoutProps(CategorizationRenderer))
119+
export default withAjvProps(
120+
withVanillaControlProps(
121+
withTranslateProps(withJsonFormsLayoutProps(CategorizationRenderer))
122+
)
110123
);

packages/vanilla-renderers/src/index.ts

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -22,89 +22,6 @@
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
THE SOFTWARE.
2424
*/
25-
import { RankedTester } from '@jsonforms/core';
26-
27-
import {
28-
BooleanCell,
29-
booleanCellTester,
30-
DateCell,
31-
dateCellTester,
32-
dateTimeCellTester,
33-
EnumCell,
34-
enumCellTester,
35-
IntegerCell,
36-
integerCellTester,
37-
NumberCell,
38-
numberCellTester,
39-
SliderCell,
40-
sliderCellTester,
41-
TextAreaCell,
42-
textAreaCellTester,
43-
TextCell,
44-
textCellTester,
45-
TimeCell,
46-
timeCellTester,
47-
} from './cells';
48-
49-
import {
50-
InputControl,
51-
inputControlTester,
52-
RadioGroupControl,
53-
radioGroupControlTester,
54-
OneOfRadioGroupControl,
55-
oneOfRadioGroupControlTester,
56-
} from './controls';
57-
58-
import {
59-
ArrayControl,
60-
arrayControlTester,
61-
Categorization,
62-
categorizationTester,
63-
LabelRenderer,
64-
labelRendererTester,
65-
TableArrayControl,
66-
tableArrayControlTester,
67-
} from './complex';
68-
69-
import {
70-
GroupLayout,
71-
groupTester,
72-
HorizontalLayout,
73-
horizontalLayoutTester,
74-
VerticalLayout,
75-
verticalLayoutTester,
76-
} from './layouts';
77-
import DateTimeCell from './cells/DateTimeCell';
78-
79-
export interface WithClassname {
80-
className?: string;
81-
}
82-
83-
/**
84-
* Additional renderer props specific to vanilla renderers.
85-
*/
86-
export interface VanillaRendererProps extends WithClassname {
87-
classNames?: { [className: string]: string };
88-
/**
89-
* Returns all classes associated with the given style.
90-
* @param {string} string the style name
91-
* @param args any additional args necessary to calculate the classes
92-
* @returns {string[]} array of class names
93-
*/
94-
getStyle?(string: string, ...args: any[]): string[];
95-
96-
/**
97-
* Returns all classes associated with the given style as a single class name.
98-
* @param {string} string the style name
99-
* @param args any additional args necessary to calculate the classes
100-
* @returns {string[]} array of class names
101-
*/
102-
getStyleAsClassName?(string: string, ...args: any[]): string;
103-
}
104-
105-
export interface WithChildren {
106-
children: any;
107-
}
10825

10926
export * from './actions';
11027
export * from './controls';
@@ -114,29 +31,4 @@ export * from './layouts';
11431
export * from './reducers';
11532
export * from './util';
11633
export * from './styles';
117-
118-
export const vanillaRenderers: { tester: RankedTester; renderer: any }[] = [
119-
{ tester: inputControlTester, renderer: InputControl },
120-
{ tester: radioGroupControlTester, renderer: RadioGroupControl },
121-
{ tester: oneOfRadioGroupControlTester, renderer: OneOfRadioGroupControl },
122-
{ tester: arrayControlTester, renderer: ArrayControl },
123-
{ tester: labelRendererTester, renderer: LabelRenderer },
124-
{ tester: categorizationTester, renderer: Categorization },
125-
{ tester: tableArrayControlTester, renderer: TableArrayControl },
126-
{ tester: groupTester, renderer: GroupLayout },
127-
{ tester: verticalLayoutTester, renderer: VerticalLayout },
128-
{ tester: horizontalLayoutTester, renderer: HorizontalLayout },
129-
];
130-
131-
export const vanillaCells: { tester: RankedTester; cell: any }[] = [
132-
{ tester: booleanCellTester, cell: BooleanCell },
133-
{ tester: dateCellTester, cell: DateCell },
134-
{ tester: dateTimeCellTester, cell: DateTimeCell },
135-
{ tester: enumCellTester, cell: EnumCell },
136-
{ tester: integerCellTester, cell: IntegerCell },
137-
{ tester: numberCellTester, cell: NumberCell },
138-
{ tester: sliderCellTester, cell: SliderCell },
139-
{ tester: textAreaCellTester, cell: TextAreaCell },
140-
{ tester: textCellTester, cell: TextCell },
141-
{ tester: timeCellTester, cell: TimeCell },
142-
];
34+
export * from './renderers';

0 commit comments

Comments
 (0)