Last active
May 14, 2024 20:01
-
-
Save Twipped/9e86624d761fb9783a9a6e6f1625dda8 to your computer and use it in GitHub Desktop.
React context for handling value selections
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
// 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