React Hooks with TypeScript
May 2, 2020 • ☕️ 3 min read
useState
// Here `isModalOpen` is infered to be Boolean,
// and `toggleModal` is infered to a func
// that recieves a Boolean argument.
const [isModalOpen, toggleModal] = React.useState(false)
Most time we might need to initial state value as null
, then set it to other type value later:
type IData = {
date: Date,
content: string
}
const [data, setData] = React.useState<IData>(null)
useRef
// This means inputEl is inmutable or read-only,
// and we'll pass it to `ref` attributes of one React element
const inputEl = useRef<HTMLInputElement>(null)
And also remember to check if inputEl is avaliable before using it, or React and Typescript will complain about it:
const PasswordInput = () => {
const inputEl = useRef<HTMLInputElement>(null)
const handleClick = () => {
// Here we check if inputEl & inputEl.current exist
// if yes, then it must be a HTMLInputElement, which
// has some specific methods like `focus()`
if (inputEl && inputEl.current) { inputEl.current.value = ''
}
}
return (
<>
<input ref={inputEl} type="text" />
<button onClick={handleClick}>Click me to clear input area!</button>
</>
)
}
// we could use optional chaining to simplify handleClick const handleClick = () => { inputEl?.current?.focus() }
About optional chaining
useEffect
You don’t need to provide any extra typings. TypeScript will check that the method signature of the function you provide is correct. This function also has a return value(for cleanups). And TypeScript will check that you provide a correct function as well.
useEffect(
() => {
const DARK_THEME_CLASS = 'dark-theme'
const bodyClassList = window.document.body.classList
if (enabled) {
bodyClassList.add(DARK_THEME_CLASS)
} else {
bodyClassList.remove(DARK_THEME_CLASS)
}
return () => {
bodyClassList.remove(DARK_THEME_CLASS)
}
}
)
useMemo
/ useCallback
Still, you don’t have to do too much, the React typings will do the best.
const users = useMemo(() => getUsers(userData), [userData])
const clickCallback = useCallback((count: number) => {
// ...
}, [count])
// Then...
clickCallback(5)
useReducer
type Todo = {
id: number,
title: string,
completed: boolean
}
type TodoState = Todo[]
type TodoAction =
| { type: 'ADD_TODO', payload: { title: string } }
| { type: 'DELETE_TODO', payload: { id: number } }
| { type: 'TOGGLE_TODO', payload: { id: number } }
const initialState = [
{ id: 1, title: 'read books', completed: false },
{ id: 2, title: 'drink water', completed: false },
{ id: 3, title: 'walk dog', completed: true }
]
let nextId = 4
const reducer = (state = initialState: TodoState, action: TodoAction) => {
switch(action.type) {
// here TypeScript make sure we don't get wrong with type name
case 'ADD_TODO':
return [
...state,
{ id: nextId++, title: action.payload.title, completed: false }
];
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.payload.id)
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
)
}
}
Custom Hooks
All we need to notice is the return value of curtom hooks:
// Code below come from: https://usehooks.com/useAsync/
const useAsync = (
asyncFunction: Promise<any>,
immediate = true: boolean
) => {
const [pending, setPending] = useState(false);
const [value, setValue] = useState(null);
const [error, setError] = useState(null);
// The execute function wraps asyncFunction and
// handles setting state for pending, value, and error.
// useCallback ensures the below useEffect is not called
// on every render, but only if asyncFunction changes.
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
// Call execute if we want to fire it right away.
// Otherwise execute can be called later, such as
// in an onClick handler.
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, pending, value, error };
// if we return an array instead of object:
return [ execute, pending, value, error ] as const
// or
return [ execute, pending, value, error ] as [
// here is the type you need to clarify
]
};