diff --git a/packages/module/src/DataViewFilters/DataViewFilters.test.tsx b/packages/module/src/DataViewFilters/DataViewFilters.test.tsx index 172ab6f6..498c87aa 100644 --- a/packages/module/src/DataViewFilters/DataViewFilters.test.tsx +++ b/packages/module/src/DataViewFilters/DataViewFilters.test.tsx @@ -2,6 +2,7 @@ import { render, fireEvent } from '@testing-library/react'; import DataViewFilters from './DataViewFilters'; import DataViewToolbar from '../DataViewToolbar'; import DataViewTextFilter from '../DataViewTextFilter'; +import { DataViewCheckboxFilter } from '../DataViewCheckboxFilter'; describe('DataViewFilters component', () => { const mockOnChange = jest.fn(); @@ -37,4 +38,25 @@ describe('DataViewFilters component', () => { fireEvent.input(input, { target: { value: 'abc' } }); expect(mockOnChange).toHaveBeenCalledWith('one', { one: 'abc' }); }); + + it('should not crash when a checkbox filter option label is a React element', () => { + // Regression test for #12536: using a React element (e.g. an icon) as a + // DataViewCheckboxFilter option label made childrenHash's JSON.stringify throw on the + // element's circular references. The tree is created inside a wrapper component so the + // label element has an owner (as in real usage), which is what triggers the cycle. + const FilterWithElementLabel = () => ( + + Active, value: 'active' } ]} + /> + + } + /> + ); + expect(() => render()).not.toThrow(); + }); }); diff --git a/packages/module/src/DataViewFilters/DataViewFilters.tsx b/packages/module/src/DataViewFilters/DataViewFilters.tsx index dc0dc218..07e78209 100644 --- a/packages/module/src/DataViewFilters/DataViewFilters.tsx +++ b/packages/module/src/DataViewFilters/DataViewFilters.tsx @@ -33,6 +33,21 @@ export interface DataViewFiltersProps extends Omit { + if (isValidElement(value)) { + const { type } = value; + const typeName = + typeof type === 'string' + ? type + : (type as { displayName?: string; name?: string }).displayName ?? (type as { displayName?: string; name?: string }).name ?? 'element'; + return { type: typeName, key: value.key }; + } + return value; +}; export const DataViewFilters = ({ children, @@ -52,7 +67,8 @@ export const DataViewFilters = ({ const childrenHash = useMemo(() => JSON.stringify( Children.map(children, (child) => isValidElement(child) ? { type: child.type, key: child.key, props: child.props } : child - ) + ), + replaceReactElements ), [ children ]); const filterItems: DataViewFilterIdentifiers[] = useMemo(() => Children.toArray(children)