Last active
September 14, 2021 05:18
-
-
Save ajitid/d999054ec788885c2b407fa0dc2a82e3 to your computer and use it in GitHub Desktop.
Control props — state management for controllable components
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
interface Props { | |
value?: string; | |
onChange?: (value: string) => void; | |
} | |
export function OutboundSelect<T>({ | |
value: externalValue, | |
onChange: externalOnChange = noop, | |
}: Props) { | |
const [value, onChange, isInternalValueUsed] = useAutoControlled( | |
externalValue, | |
externalOnChange, | |
"" | |
); | |
function handleChange(event: React.ChangeEvent<{ value: unknown }>) { | |
const value = event.target.value; | |
if (typeof value !== "string") return; | |
onChange(value); | |
// Passed `onChange` handler should be called irrespective of whether | |
// we are using internal state or not. | |
if (isInternalValueUsed) { | |
externalOnChange(value); | |
} | |
} |
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
import { useRef, useState } from "react"; | |
/** | |
* Replacement for `useState` to do two-way data binding in both controlled and | |
* in uncontrolled form. | |
* | |
* When should you use this hook? If React (or Material UI) throw an error in | |
* console about a component changing an input from uncontrolled to controlled. | |
*/ | |
export const useAutoControlled = <T>( | |
value: T | undefined, | |
setValue: (value: T) => unknown, | |
initialValue: T | |
): [ | |
value: T | undefined, | |
setValue: (value: T) => unknown, | |
isInternalValueUsed: boolean | |
] => { | |
/* | |
We don't want to switch from uncontrolled to controlled state | |
in the midst of using the input. So we decide upfront which one of | |
them to use by defining this ref. | |
*/ | |
const shouldUseInternalValueRef = useRef(value === undefined); | |
const [internalValue, setInternalValue] = useState(initialValue); | |
return shouldUseInternalValueRef.current | |
? [internalValue, (value: T) => setInternalValue(value), true] | |
: [value, setValue, false]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that in
usage.tsx
, unlike React we aren't showing any warning when:value
is passed butonChange
handler isn't, or onlyvalue
is passed but is not markedreadOnly
using a propThis needs to be handled using
NODE_ENV
var and warning package.