Last active
June 7, 2023 02:22
-
-
Save scwood/d1efeab605befa8e30d0f158e8f21580 to your computer and use it in GitHub Desktop.
Using fluent selection in a "controlled" manner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IObjectWithKey, Selection, SelectionMode } from '@fluentui/react'; | |
import { useEffect, useRef } from 'react'; | |
/** Options interface for the `useControlledFluentSelection` hook. */ | |
export interface ControlledSelectionOptions<T> { | |
/** The currently selected items. */ | |
selectedItems: T[]; | |
/** Optional selection mode. */ | |
selectionMode?: SelectionMode; | |
/** Event handler called when the selected items change. */ | |
onSelectionChanged?: (selectedItems: T[]) => void; | |
/** Get key function passed to the Fluent selection object. */ | |
getKey: (item: T) => string; | |
} | |
/** | |
* This hook lets you use control the state used by the Fluent selection | |
* object. This let's you more easily do things like programmatically change the | |
* selected items in a `DetailsList`, while also keeping a state variable in | |
* sync with a user selection on the table itself. | |
* | |
* @example | |
* const [selectedThings, setSelectedThings] = useState<Thing[]>([]); | |
* | |
* const selection = useControlledFluentSelection({ | |
* selectedItems: selectedThings, | |
* onSelectionChanged: setSelectedThings, | |
* getKey: (thing) => thing.id, | |
* }); | |
* | |
* function someHandler() { | |
* setSelectedThings([someThing]); // Details list will update | |
* } | |
* | |
* return <DetailsList selection={selection} ... /> | |
*/ | |
export function useControlledFluentSelection<T>(options: ControlledSelectionOptions<T>) { | |
const { selectedItems, selectionMode, onSelectionChanged, getKey } = options; | |
const onSelectionChangedRef = useRef(onSelectionChanged); | |
const getKeyRef = useRef(getKey); | |
const shouldCallChangeHandlerRef = useRef(true); | |
const selectionRef = useRef( | |
new Selection({ | |
selectionMode, | |
getKey: (item: IObjectWithKey) => getKeyRef.current(item as T), | |
onSelectionChanged: () => { | |
if (shouldCallChangeHandlerRef.current) { | |
onSelectionChangedRef.current?.(selectionRef.current.getSelection() as T[]); | |
} | |
}, | |
}), | |
); | |
// Save the latest callbacks so they're available to the Fluent Selection | |
useEffect(() => { | |
onSelectionChangedRef.current = onSelectionChanged; | |
getKeyRef.current = getKey; | |
}, [onSelectionChanged, getKey]); | |
// Sync selectedItems to the Fluent Selection | |
useEffect(() => { | |
// We don't want the selection object to call `onSelectionChanged` for | |
// these updates because it creates an infinite loop. | |
shouldCallChangeHandlerRef.current = false; | |
if (selectedItems.length > 0 && selectedItems.length === selectionRef.current.getItems().length) { | |
selectionRef.current.setAllSelected(true); | |
} else { | |
selectionRef.current.setAllSelected(false); | |
for (const item of selectedItems) { | |
selectionRef.current.setKeySelected(getKeyRef.current(item), true, false); | |
} | |
} | |
shouldCallChangeHandlerRef.current = true; | |
}, [selectedItems]); | |
return selectionRef.current; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment