11import { Dropdown , MenuToggle , MenuToggleElement , ToolbarFilter , ToolbarFilterProps , TreeView , TreeViewDataItem } from '@patternfly/react-core'
2- import React , { FC , useState , useRef , useEffect } from 'react'
2+ import React , { FC , useState , useRef , useEffect , useCallback } from 'react'
33import { createUseStyles } from 'react-jss'
44
55/** This style is needed so the tree filter dropdown looks like the basic filter dropdow */
@@ -59,17 +59,16 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
5959 const hasCalledInitialOnChange = useRef ( false ) ;
6060
6161 // Helper function to expand all nodes in the tree
62- const expandAllNodes = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] => {
63- return items . map ( item => ( {
62+ const expandAllNodes = useCallback ( ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] =>
63+ items . map ( item => ( {
6464 ...item ,
6565 defaultExpanded : true ,
6666 children : item . children ? expandAllNodes ( item . children ) : undefined
67- } ) ) ;
68- } ;
67+ } ) ) , [ ] ) ;
6968
7069 // Helper function to set pre-selected items
71- const setPreSelectedItems = ( items : TreeViewDataItem [ ] , selectedIds : string [ ] ) : TreeViewDataItem [ ] => {
72- return items . map ( item => {
70+ const setPreSelectedItems = useCallback ( ( items : TreeViewDataItem [ ] , selectedIds : string [ ] ) : TreeViewDataItem [ ] =>
71+ items . map ( item => {
7372 const isSelected = selectedIds . includes ( String ( item . id ) ) ;
7473 const hasSelectedChildren = item . children ?. some ( child => selectedIds . includes ( String ( child . id ) ) ) ?? false ;
7574
@@ -81,11 +80,10 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
8180 } : undefined ,
8281 children : item . children ? setPreSelectedItems ( item . children , selectedIds ) : undefined
8382 } ;
84- } ) ;
85- } ;
83+ } ) , [ ] ) ;
8684
8785 // Generic helper to collect items from tree based on predicate
88- const collectTreeItems = (
86+ const collectTreeItems = useCallback ( (
8987 items : TreeViewDataItem [ ] ,
9088 predicate : ( item : TreeViewDataItem ) => boolean ,
9189 leafOnly = false
@@ -104,16 +102,25 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
104102
105103 items . forEach ( item => collect ( item ) ) ;
106104 return collected ;
107- } ;
105+ } , [ ] ) ;
108106
109107 // Helper function to get all checked items (not just leaf nodes)
110- const getAllCheckedItems = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] => {
111- return collectTreeItems ( items , item => item . checkProps ?. checked === true , false ) ;
112- } ;
108+ const getAllCheckedItems = useCallback ( ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] =>
109+ collectTreeItems ( items , item => item . checkProps ?. checked === true , false ) , [ collectTreeItems ] ) ;
110+
111+ // Get all checked leaf items (returns array of names)
112+ const getAllCheckedLeafItems = useCallback ( ( items : TreeViewDataItem [ ] ) : string [ ] =>
113+ collectTreeItems (
114+ items ,
115+ item => item . checkProps ?. checked === true ,
116+ true
117+ ) . map ( item => String ( item . name ) ) , [ collectTreeItems ] ) ;
113118
114119 // Initialize tree data with defaultExpanded and defaultSelected (only on first mount)
115120 useEffect ( ( ) => {
116- if ( ! items ) return ;
121+ if ( ! items ) {
122+ return ;
123+ }
117124
118125 let initializedData = [ ...items ] ;
119126
@@ -132,7 +139,7 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
132139 if ( isInitialMount . current ) {
133140 isInitialMount . current = false ;
134141 }
135- } , [ items , defaultExpanded ] ) ;
142+ } , [ items , defaultExpanded , expandAllNodes , setPreSelectedItems , defaultSelected ] ) ;
136143
137144 // Call onChange and onSelect after tree data is initialized with default selections
138145 useEffect ( ( ) => {
@@ -156,7 +163,7 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
156163 hasCalledInitialOnChange . current = true ;
157164 }
158165 }
159- } , [ treeData ] ) ;
166+ } , [ treeData , defaultSelected . length , onChange , onSelect , getAllCheckedItems , getAllCheckedLeafItems ] ) ;
160167
161168 // Sync tree checkboxes when value prop changes (when clearAllFilters is called)
162169 useEffect ( ( ) => {
@@ -165,18 +172,17 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
165172
166173 // Only update if there are checked items that need to be unchecked
167174 if ( currentCheckedItems . length > 0 ) {
168- const uncheckRecursive = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] => {
169- return items . map ( item => ( {
175+ const uncheckRecursive = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] =>
176+ items . map ( item => ( {
170177 ...item ,
171178 checkProps : item . checkProps ? { ...item . checkProps , checked : false } : undefined ,
172179 children : item . children ? uncheckRecursive ( item . children ) : undefined
173180 } ) ) ;
174- } ;
175181
176182 setTreeData ( uncheckRecursive ( treeData ) ) ;
177183 }
178184 }
179- } , [ value ] ) ;
185+ } , [ value , treeData , getAllCheckedLeafItems ] ) ;
180186
181187 // Check if all children are checked (recursive)
182188 const areAllChildrenChecked = ( item : TreeViewDataItem ) : boolean => {
@@ -202,7 +208,9 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
202208 }
203209 if ( item . children ) {
204210 const found = findItemByName ( item . children , name ) ;
205- if ( found ) return found ;
211+ if ( found ) {
212+ return found ;
213+ }
206214 }
207215 }
208216 return null ;
@@ -216,31 +224,32 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
216224 }
217225 if ( item . children ) {
218226 const found = findParentById ( item . children , childId ) ;
219- if ( found ) return found ;
227+ if ( found ) {
228+ return found ;
229+ }
220230 }
221231 }
222232 return null ;
223233 } ;
224234
225- // Get all checked leaf items (returns array of names)
226- const getAllCheckedLeafItems = ( items : TreeViewDataItem [ ] ) : string [ ] => {
227- return collectTreeItems (
228- items ,
229- item => item . checkProps ?. checked === true ,
230- true
231- ) . map ( item => String ( item . name ) ) ;
232- } ;
233-
234235 // Update parent checkbox states based on children (recursive)
235236 const onCheckParentHandle = ( childId : string ) : void => {
236237 const parent = findParentById ( treeData , childId ) ;
237- if ( ! parent ) return ;
238+ if ( ! parent ) {
239+ return ;
240+ }
238241
239242 if ( parent . checkProps ) {
240243 const allChildrenChecked = areAllChildrenChecked ( parent ) ;
241244 const someChildrenChecked = areSomeChildrenChecked ( parent ) ;
242245
243- parent . checkProps . checked = allChildrenChecked ? true : someChildrenChecked ? null : false ;
246+ if ( allChildrenChecked ) {
247+ parent . checkProps . checked = true ;
248+ } else if ( someChildrenChecked ) {
249+ parent . checkProps . checked = null ;
250+ } else {
251+ parent . checkProps . checked = false ;
252+ }
244253 }
245254
246255 if ( parent . id ) {
@@ -270,7 +279,7 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
270279 setTreeData ( prev => [ ...prev ] ) ;
271280
272281 const selectedValues = getAllCheckedLeafItems ( treeData ) ;
273- onChange ?.( event as any , selectedValues ) ;
282+ onChange ?.( undefined , selectedValues ) ;
274283
275284 if ( onSelect ) {
276285 const selectedItems = getAllCheckedItems ( treeData ) ;
@@ -281,7 +290,9 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
281290 // Clear a specific filter by name (when label chip is removed)
282291 const onFilterSelectorClear = ( itemName : string ) => {
283292 const treeViewItem = findItemByName ( treeData , itemName ) ;
284- if ( ! treeViewItem ) return ;
293+ if ( ! treeViewItem ) {
294+ return ;
295+ }
285296
286297 onCheckHandle ( treeViewItem , false ) ;
287298 if ( treeViewItem . id ) {
@@ -291,13 +302,12 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
291302
292303 // Uncheck all items in the tree
293304 const uncheckAllItems = ( ) => {
294- const uncheckRecursive = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] => {
295- return items . map ( item => ( {
305+ const uncheckRecursive = ( items : TreeViewDataItem [ ] ) : TreeViewDataItem [ ] =>
306+ items . map ( item => ( {
296307 ...item ,
297308 checkProps : item . checkProps ? { ...item . checkProps , checked : false } : undefined ,
298309 children : item . children ? uncheckRecursive ( item . children ) : undefined
299310 } ) ) ;
300- } ;
301311
302312 const updatedTreeData = uncheckRecursive ( treeData ) ;
303313 setTreeData ( updatedTreeData ) ;
@@ -329,20 +339,20 @@ export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
329339
330340 return (
331341 < ToolbarFilter
332- key = { filterId }
333- data-ouia-component-id = { ouiaId }
334- labels = { value . map ( item => ( { key : item , node : item } ) ) }
335- deleteLabel = { ( _ , label ) => {
336- const labelKey = typeof label === 'string' ? label : label . key ;
337- onChange ?.( undefined , value . filter ( item => item !== labelKey ) ) ;
338- onFilterSelectorClear ( labelKey ) ;
339- } }
340- deleteLabelGroup = { uncheckAllItems }
341- categoryName = { title }
342- showToolbarItem = { showToolbarItem } >
343- { dropdown }
344- </ ToolbarFilter >
342+ key = { filterId }
343+ data-ouia-component-id = { ouiaId }
344+ labels = { value . map ( item => ( { key : item , node : item } ) ) }
345+ deleteLabel = { ( _ , label ) => {
346+ const labelKey = typeof label === 'string' ? label : label . key ;
347+ onChange ?.( undefined , value . filter ( item => item !== labelKey ) ) ;
348+ onFilterSelectorClear ( labelKey ) ;
349+ } }
350+ deleteLabelGroup = { uncheckAllItems }
351+ categoryName = { title }
352+ showToolbarItem = { showToolbarItem } >
353+ { dropdown }
354+ </ ToolbarFilter >
345355 )
346356}
347357
348- export default DataViewTreeFilter ;
358+ export default DataViewTreeFilter ;
0 commit comments