I'm trying to experiment custom Hooks with TS (quite new on both). This useOption
should take etiher a number
or a boolean
, and return the value
with the same type, and the changer function. This because an option should be <input>
with 'range' type, and one <input>
as checkbox (but this could be extended with other input types). So I have an <Options />
component like this:
const Options = () => {
const [pieces, setPieces] = useOption(10);
const [speed, setSpeed] = useOption(5);
const [sound, setSound] = useOption(false);
return (
<div className='Options'>
<input
type='range'
value={pieces}
onChange={setPieces}
min='5'
max='25' />
<p>{pieces}</p>
<input
type='range'
value={speed}
onChange={setSpeed}
min='1'
max='10' />
<p>{speed}</p>
<input
type='checkbox'
value={sound}
onChange={setSound} />
<p>{sound ? 'Sound on' : 'Sound off'}</p>
<button
className='start-btn'
onClick={handleClick}>Start game</button>
</div>
)
};
And a custom hook useOption.
const useOption = (initialValue: number | boolean) => {
const [value, setValue] = useState(initialValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (typeof(e.currentTarget.value) === 'boolean') {
const newValue = !value;
setValue(newValue);
}
else {
const newValue = Number(e.currentTarget.value);
setValue(newValue)
}
};
return [value, handleChange] as const;
};
I thought, that with the typeof
type guard and the use of const
(like written in this page), TS recognized the returned type, but I still have errors about it, showing me the error over the value
property.
Type 'number | boolean' is not assignable to type 'string | number | readonly string[] | undefined'.
Type 'false' is not assignable to type 'string | number | readonly string[] | undefined'.ts(2322)
index.d.ts(2256, 9): The expected type comes from property 'value' which is declared here on type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'
Any suggestion about to solve it?
CodePudding user response:
You can make the hook generic
const useOption = <T extends number | boolean>(initialValue: T) => {
const [value, setValue] = React.useState(initialValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (typeof e.currentTarget.value === 'boolean') {
const newValue = !value;
setValue(newValue as T);
} else {
const newValue = Number(e.currentTarget.value);
setValue(newValue as T);
}
};
return [value, handleChange] as const;
};
And pass type in the component
export default function App() {
const [pieces, setPieces] = useOption<number>(10);
const [speed, setSpeed] = useOption<number>(5);
const [sound, setSound] = useOption<boolean>(false);
return (
<div className="Options">
<input
type="range"
value={pieces}
onChange={setPieces}
min="5"
max="25"
/>
<p>{pieces}</p>
<input type="range" value={speed} onChange={setSpeed} min="1" max="10" />
<p>{speed}</p>
<input type="checkbox" checked={sound} onChange={setSound} /> // use checked in here
<p>{sound ? 'Sound on' : 'Sound off'}</p>
</div>
);
}