How to Avoid the useState Hell in React.js
by using useReducer
Post by Pedro Resende on the 27 of January of 2023 at 08:45
Tags: devdevelopmentreact.jsuseStatetypescripttutorialuseReducer
This week, I had to implement a member management form, using React.js.
It was a simple form to add, or edit, users to the given platform, therefore I've discarded the need to install additional libraries, like React Hook Form or Formik
At the end of the implementation, I had something like the following, in terms of useStates:
const [showNewMemberProfile, setShowNewMemberProfile] = useState<boolean>(false)
const [showMemberProfile, setShowMemberProfile] = useState<boolean>(false)
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false)
const [isLoading, setLoading] = useState<boolean>(false)
const [hasError, setError] = useState<boolean>(false)
const [name, setName] = useState<string>('')
const [email, setEmail] = useState<string>('')
const [role, setRole] = useState<string>('')
it's basically a nightmare to deal with. If you need to reset the form content you need to do something like
setName('')
setEmail('')
setRole('')
setShowMemberProfile(false)
setLoading(false)
That's why I've decided to investigate how to do this more cleanly, and I've concluded the hero for this job was useReducer
.
The useReducer, it's a React hook that lets you add a reducer to your component
Let's start by declaring the initialState of the form
const initialState = {
showNewMemberProfile: false,
showMemberProfile: false,
showDeleteModal: false,
isLoading: false,
hasError: false,
name: '',
email: '',
role: '',
}
in my particular case, I've also declared the actions for this reducer by using the following enumerator
enum ACTIONS {
LOADING = 'LOADING',
DELETE_MODAL = 'DELETE_MODAL',
SHOW_MEMBER_PROFILE = 'SHOW_MEMBER_PROFILE',
SHOW_NEW_MEMBER_PROFILE = 'SHOW_NEW_MEMBER_PROFILE',
ERROR = 'ERROR',
FIELD = 'FIELD',
RESET = 'RESET'
}
Finally, the most important part it's the reducer, which in this case is the following
interface InitialState {
showNewMemberProfile: boolean,
showMemberProfile: boolean,
showDeleteModal: boolean,
isLoading: boolean,
hasError: boolean,
name: string,
email: string,
role: string,
}
interface Action {
type: ACTIONS,
payload?: any,
field?: string
}
const reducer = (state: InitialState, action: Action) => {
const { type, payload, field } = action
if (type === ACTIONS.LOADING) {
return {
...state,
isLoading: payload
}
}
if (type === ACTIONS.DELETE_MODAL) {
return {
...state,
showDeleteModal: payload
}
}
if (type === ACTIONS.SHOW_MEMBER_PROFILE) {
return {
...state,
showMemberProfile: payload
}
}
if (type === ACTIONS.SHOW_NEW_MEMBER_PROFILE) {
return {
...state,
showNewMemberProfile: payload
}
}
if (type === ACTIONS.FIELD && field) {
return {
...state,
[field]: payload
}
}
if (type === ACTIONS.ERROR) {
return {
...state,
hasError: payload
}
}
if (type === ACTIONS.RESET) {
return {
...state,
...initialState
}
}
throw Error('Unknown action: ' + action.type)
}
And those useState will become
const [formState, dispatch] = useReducer(reducer, initialState)
Now, to reset the form state to its initial state I just need to call
dispatch({ type: ACTIONS.RESET })
to set a given field, it will become
(value: string) => dispatch({ type: ACTIONS.FIELD, payload: value, field: 'name' })
And that's all. Please let me know what you about this tutorial, would you change anything or add something?