-
Notifications
You must be signed in to change notification settings - Fork 39
Typescript: (5) Function Component State, Events, Event Handlers
Mithi Sevilla edited this page Mar 30, 2021
·
1 revision
Typescript can infer types based on initial value otherwise:
const [count, setCount] = useState<number | null>(null)
type State = {
count: number
}
type Increment = {
readonly type: "increment"
readonly step: number
}
type Decrement = {
readonly type: "decrement"
readonly step: number
}
type Actions = Increment | Decrement
const reducer = (previous: State, action: Actions): State => {
switch(action.type) {
case 'increment':
return { count: previous.count + action.step }
case 'decrement':
return { count: previous.count - action.step }
}
return previous // this line should never be reached
// but it makes typescript happy
}
IMPORTANT: Look at this error
type Double = {
readonly type: 'double'
}
type Actions = Increment | Decrement | Double
const reducer = (previous: State, action: Actions): State => {
switch(action.type) {
case 'increment':
return { count: previous.count + action.step }
case 'decrement':
return { count: previous.count - action.step }
default:
neverReached(action)
// ERROR: Argument of type 'Double' is not assignable to parameter of type 'never'
}
return Previous
}
const neverReached = (never: never) => {}
To remove the error replace your reducer with this
const reducer = (previous: State, action: Actions) => {
switch(action.type) {
case 'increment':
return { count: previous.count + action.step }
case 'decrement':
return { count: previous.count - action.step }
case 'double':
return { count: previous.count * 2}
default:
neverReached(action)
}
return previous // when you return this
// you don't actually need to specify the return type anymore
// it can be inferred by typescript
}
const neverReached = (never: never) => {}
Now we can use it like this
type CounterProps = {initialCount: number}
const Counter = ({initialCount = 0}: CounterProps) => {
const [state, dispatch] = React.useReducer(reducer, {count: initialCount})
return <>
{state.count}
<button
onClick={() => dispatch({type: "increment", step: 1})}>add one</button>
<button
onClick={() => dispatch({type: "decrement", step: 2})}>subtract one</button>
<button
onClick={() => dispatch({type: "double"})} >double</button>
</>
}
You can also declare
const [state, dispatch] = useReducer<Reducer<State, Actions>>(initialState, reducer)
type Event = ChangeEvent<HTMLInputElement>
const Input = () => {
const [value, setValue] = useState("")
const handleChange = (e: Event) => setValue(e.currentTarget.value)
return <input
type="text"
value={value}
onChange={handleChange}
/>
}
type Event = ChangeEvent<HTMLInputElement>
type Props = {
onSearch?: (criteria: string) => void
}
const Searchbox = ({ onSearch }: Props) => {
const [criteria, setCriteria] = useState("")
const handleChange = (e: Event) => {
setCriteria(e.currentTarget.value)
if(onSearch) {
onSearch(e.currentTarget.value)
}
}
return <input
type="text"
value={criteria}
onChange={handleChange}
/>
}
Other examples of events
type Event = React.KeyboardEvent<HTMLInputElement>
type AnotherEvent = React.FormEvent<HTMLFormElement>