Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Last active June 14, 2023 17:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kentcdodds/2164879e10242bd830b8cdeb6c206ec4 to your computer and use it in GitHub Desktop.
Save kentcdodds/2164879e10242bd830b8cdeb6c206ec4 to your computer and use it in GitHub Desktop.
A simpler implementation of control props
// control props
import React from 'react'
import {Switch} from '../switch'
const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
const noop = () => {}
function toggleReducer(state, {type, initialState}) {
switch (type) {
case useToggle.types.toggle: {
return {on: !state.on}
}
case useToggle.types.reset: {
return initialState
}
default:
throw new Error(`Unsupported type: ${type}`)
}
}
function useToggle({
onChange = noop,
initialOn = false,
reducer = toggleReducer,
on: controlledOn,
} = {}) {
const {current: initialState} = React.useRef({on: initialOn})
const [state, dispatch] = React.useReducer(reducer, initialState)
const on = controlledOn === undefined ? state.on : controlledOn
function dispatchWithOnChange(action) {
dispatch(action)
onChange(reducer({...state, on}, action), action)
}
const toggle = () => dispatchWithOnChange({type: useToggle.types.toggle})
const reset = () =>
dispatchWithOnChange({type: useToggle.types.reset, initialState})
function getTogglerProps({onClick, ...props} = {}) {
return {
'aria-pressed': on,
onClick: callAll(onClick, toggle),
...props,
}
}
return {
on,
reset,
toggle,
getTogglerProps,
}
}
useToggle.reducer = toggleReducer
useToggle.types = {
toggle: 'toggle',
reset: 'reset',
}
function Toggle({on: controlledOn, onChange}) {
const {on, getTogglerProps} = useToggle({on: controlledOn, onChange})
const props = getTogglerProps({on})
return <Switch {...props} />
}
function Usage() {
const [bothOn, setBothOn] = React.useState(false)
const [timesClicked, setTimesClicked] = React.useState(0)
function handleToggleChange(state, action) {
if (action.type === useToggle.types.toggle && timesClicked >= 4) {
return
}
setBothOn(state.on)
setTimesClicked(c => c + 1)
}
function handleResetClick(params) {
setBothOn(false)
setTimesClicked(0)
}
return (
<div>
<div>
<Toggle on={bothOn} onChange={handleToggleChange} />
<Toggle on={bothOn} onChange={handleToggleChange} />
</div>
{timesClicked > 4 ? (
<div data-testid="notice">
Whoa, you clicked too much!
<br />
</div>
) : (
<div data-testid="click-count">Click count: {timesClicked}</div>
)}
<button onClick={handleResetClick}>Reset</button>
<hr />
<div>
<div>Uncontrolled Toggle:</div>
<Toggle
onChange={(...args) =>
console.info('Uncontrolled Toggle onChange', ...args)
}
/>
</div>
</div>
)
}
Usage.title = 'Control Props'
export default Usage
export {Toggle}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment