Skip to content

Instantly share code, notes, and snippets.

@Twipped
Last active May 14, 2024 20:01
Show Gist options
  • Save Twipped/9e86624d761fb9783a9a6e6f1625dda8 to your computer and use it in GitHub Desktop.
Save Twipped/9e86624d761fb9783a9a6e6f1625dda8 to your computer and use it in GitHub Desktop.
React context for handling value selections
// Warning, this is dry code
import PropTypes from 'prop-types';
import { useCallback, useContext, createContext, useState } from 'react';
import useMemoObject from '@twipped/hooks/useMemoObject';
import useDerivedState from '@twipped/hooks/useDerivedState';
export const SelectableContext = createContext(null);
SelectableContext.displayName = 'SelectableContext';
export function useSelectContext() {
return useContext(SelectableContext);
}
export function MultiSelectableProvider ({
value,
onChange = null,
children
}) {
// The derived state ensures a controlled state that resets when the input
// changes from the current state value. Because `value` is an array, we can
// pass it directly as the dependency.
const [currentSelection, setSelection] = useDerivedState(
() => new Set(value),
value
);
const [,update] = useState();
const onToggleSelection = useCallback((value, toggle) => {
if (typeof toggle === 'undefined') {
toggle = !currentSelection.has(value);
}
if (toggle) {
currentSelection.add(value);
} else {
currentSelection.delete(value);
}
update({}); // forces component refresh
onChange?.(Array.from(currentSelection));
}, [onChange]);
const context = useMemoObject({ currentSelection, onToggleSelection, mode: 'multiple' })
return (
<SelectableContext.Provider value={context}>
{children}
</SelectableContext.Provider>
);
}
MultiSelectableProvider.propTypes = {
value: PropTypes.arrayOf(PropTypes.any),
onChange: PropTypes.func,
};
export function SelectableProvider({
value,
onChange = null,
children
}) {
// The derived state ensures a controlled state that resets when the input
// changes from the current state value.
const [currentSelection, setSelection] = useDerivedState(() => value, [value]);
const onSelection = useCallback((value) => {
setSelection(value);
onChange?.(value);
}, [onChange]);
const context = useMemoObject({ currentSelection, onSelection, mode: 'single' })
return (
<SelectableContext.Provider value={context}>
{children}
</SelectableContext.Provider>
);
}
SelectableProvider.propTypes = {
value: PropTypes.any,
onChange: PropTypes.func,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment