Skip to content

Instantly share code, notes, and snippets.

@scwood
Last active June 7, 2023 02:22
Show Gist options
  • Save scwood/d1efeab605befa8e30d0f158e8f21580 to your computer and use it in GitHub Desktop.
Save scwood/d1efeab605befa8e30d0f158e8f21580 to your computer and use it in GitHub Desktop.
Using fluent selection in a "controlled" manner
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