From 10e00dcdb840356048513b75bf468db58813799e Mon Sep 17 00:00:00 2001 From: Titani Labaj <39532947+tlabaj@users.noreply.github.com> Date: Thu, 16 Apr 2026 09:38:53 -0400 Subject: [PATCH 1/3] fix(Toolbar filter): Fixed null exception in Toolbar filter (#12352) * fix(Toolbar filter): Fixed null exception in Toolbar filter * update from pr review --- .../src/components/Toolbar/ToolbarFilter.tsx | 25 +++++---- .../Toolbar/__tests__/ToolbarFilter.test.tsx | 54 +++++++++++++++++++ 2 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 packages/react-core/src/components/Toolbar/__tests__/ToolbarFilter.test.tsx diff --git a/packages/react-core/src/components/Toolbar/ToolbarFilter.tsx b/packages/react-core/src/components/Toolbar/ToolbarFilter.tsx index 7284e853426..2ae4ba23a28 100644 --- a/packages/react-core/src/components/Toolbar/ToolbarFilter.tsx +++ b/packages/react-core/src/components/Toolbar/ToolbarFilter.tsx @@ -130,26 +130,29 @@ class ToolbarFilter extends Component { ) : null; if (!_isExpanded && this.state.isMounted) { + const collapsedLabelPortalTarget = labelGroupContentRef?.current?.firstElementChild ?? null; return ( {showToolbarItem && {children}} - {labelGroupContentRef?.current?.firstElementChild !== null && - ReactDOM.createPortal(labelGroup, labelGroupContentRef.current.firstElementChild)} + {collapsedLabelPortalTarget !== null && ReactDOM.createPortal(labelGroup, collapsedLabelPortalTarget)} ); } return ( - {({ labelContainerRef }) => ( - - {showToolbarItem && {children}} - {labelContainerRef.current && ReactDOM.createPortal(labelGroup, labelContainerRef.current)} - {expandableLabelContainerRef && - expandableLabelContainerRef.current && - ReactDOM.createPortal(labelGroup, expandableLabelContainerRef.current)} - - )} + {({ labelContainerRef }) => { + const labelContainer = labelContainerRef?.current ?? null; + return ( + + {showToolbarItem && {children}} + {labelContainer !== null && ReactDOM.createPortal(labelGroup, labelContainer)} + {expandableLabelContainerRef && + expandableLabelContainerRef.current && + ReactDOM.createPortal(labelGroup, expandableLabelContainerRef.current)} + + ); + }} ); } diff --git a/packages/react-core/src/components/Toolbar/__tests__/ToolbarFilter.test.tsx b/packages/react-core/src/components/Toolbar/__tests__/ToolbarFilter.test.tsx new file mode 100644 index 00000000000..4e37176b9b8 --- /dev/null +++ b/packages/react-core/src/components/Toolbar/__tests__/ToolbarFilter.test.tsx @@ -0,0 +1,54 @@ +import { createRef } from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import { ToolbarContext } from '../ToolbarUtils'; +import { ToolbarFilter } from '../ToolbarFilter'; + +describe('ToolbarFilter', () => { + it('renders when ToolbarFilter is not under Toolbar or ToolbarContent (default context)', () => { + const deleteLabel = jest.fn(); + const deleteLabelGroup = jest.fn(); + + expect(() => + render( + + filter content + + ) + ).not.toThrow(); + + expect(screen.getByText('filter content')).toBeInTheDocument(); + }); + + it('does not throw when labelGroupContentRef.current is still null while collapsed (listed filters)', () => { + const labelGroupContentRef = createRef(); + expect(labelGroupContentRef.current).toBeNull(); + + expect(() => + render( + {}, + labelGroupContentRef, + updateNumberFilters: () => {}, + numberOfFilters: 0, + clearAllFilters: () => {} + }} + > + + filter content + + + ) + ).not.toThrow(); + + expect(screen.getByText('filter content')).toBeInTheDocument(); + }); +}); From bc8c125e728a4236ca8579987f931ef2b4fd7ae5 Mon Sep 17 00:00:00 2001 From: Donald Labaj Date: Fri, 17 Apr 2026 13:59:36 -0400 Subject: [PATCH 2/3] fix: cherry picked null pointer exception for toolbar filter. --- .../Select/examples/CheckboxSelectDemo.tsx | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx diff --git a/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx b/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx deleted file mode 100644 index 17dc86a16a8..00000000000 --- a/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useMemo, useState, FunctionComponent } from 'react'; -import { CheckboxSelect, CheckboxSelectOption } from '@patternfly/react-templates'; - -const Options: { content: string; value: string; description?: string; isDisabled?: boolean }[] = [ - { content: 'Option 1', value: 'option-1' }, - { content: 'Option 2', value: 'option-2', description: 'Option with description' }, - { content: 'Option 3', value: 'option-3', isDisabled: true }, - { content: 'Option 4', value: 'option-4' } -]; - -export const SelectBasic: FunctionComponent = () => { - const [selected, setSelected] = useState(['option-2']); - - const initialOptions = useMemo( - () => Options.map((o) => ({ ...o, selected: selected.includes(o.value) })), - [selected] - ); - - return ( - { - const val = String(value); - setSelected((prevSelected) => - prevSelected.includes(val) ? prevSelected.filter((item) => item !== val) : [...prevSelected, String(val)] - ); - }} - /> - ); -}; From 9746cea131ab2aa06c42d511eff0f40da41f0fd6 Mon Sep 17 00:00:00 2001 From: Donald Labaj Date: Fri, 17 Apr 2026 14:05:37 -0400 Subject: [PATCH 3/3] chore: added checkbox select demob back. --- .../Select/examples/CheckboxSelectDemo.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx diff --git a/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx b/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx new file mode 100644 index 00000000000..17dc86a16a8 --- /dev/null +++ b/packages/react-templates/src/components/Select/examples/CheckboxSelectDemo.tsx @@ -0,0 +1,30 @@ +import { useMemo, useState, FunctionComponent } from 'react'; +import { CheckboxSelect, CheckboxSelectOption } from '@patternfly/react-templates'; + +const Options: { content: string; value: string; description?: string; isDisabled?: boolean }[] = [ + { content: 'Option 1', value: 'option-1' }, + { content: 'Option 2', value: 'option-2', description: 'Option with description' }, + { content: 'Option 3', value: 'option-3', isDisabled: true }, + { content: 'Option 4', value: 'option-4' } +]; + +export const SelectBasic: FunctionComponent = () => { + const [selected, setSelected] = useState(['option-2']); + + const initialOptions = useMemo( + () => Options.map((o) => ({ ...o, selected: selected.includes(o.value) })), + [selected] + ); + + return ( + { + const val = String(value); + setSelected((prevSelected) => + prevSelected.includes(val) ? prevSelected.filter((item) => item !== val) : [...prevSelected, String(val)] + ); + }} + /> + ); +};