Let's say I have a component CounterChanger
. It is responsible for changing a counter when clicked based on a function which accepts the previous value.
interface CounterChangerProps {
valueChangingFunc: (value: number) => number
value: number
// Same as a hook state setter
onChange: (newValue: number | ((value: number) => number)) => void)
}
Inside the implementation, CounterChangerProps
simply passes valueChangingFunc
into onChange
to take advantage of the signature.
const CounterChanger = ({ valueChangingFunc, value, onChange }) => {
return <Button
onClick={() => onChange(previous => valueChangingFunc(previous))}
>
Click me {value}
</Button>
}
The following patterns works fine if this component is isolated, and the setValue
is passed directly into onChange
.
const CounterWrapper = () => {
const [value, setValue] = useState(1)
// Double it
return <CounterChanger
value={value}
onChange={setValue}
valueChangingFunc={(previous) => previous*2}
/>
}
But what if I want the outside world to know of the value inside CounterWrapper
? I want CounterWrapper
to implement an onChange
which doesn't need to worry about the callback variation.
interface CounterWrapperProps {
onChange: (value: number) => void
value: number
}
How can I make this happen? I can't replace setValue
for onChange
because it doesn't implement the necessary callback variation.
What I have come up with
The best implementation I could come up with was this"
const handleOnChange: React.Dispatch<React.SetStateAction<number>> = (
newValue
) => {
setCurrentValue((previous) => {
if (typeof newValue === 'number') {
const result = newValue
onChange?.(newValue)
return result
} else {
const result = newValue(previous)
onChange?.(result)
return result
}
})
}
Is this okay to do? It feels like setter abuse to me.
Update: It is abuse
What I don't want to do
A tempting solution is to use effects for this.
useEffect(() => {
onChange(value)
}, [value])
I don't want to do this because it makes me go down a rabbit hole of syncing state with effects which is Not The React Way.