Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The right way to use the Hook useReducer for a complex state

Trying to catch up on the React Hooks. I'm reading that they recommend the use of the Hook useReducer when dealing with a complex state. But my doubt starts from the following scheme:

Using React + Typescript, suppose I have a state with several fields (I'll give an example with classes):

type Person = {
   username: string,
   email: string
}

type Pet = {
   name: string,
   age: number
}

this.state: MyState = {
    person: Person,
    pet: Pet,
    loading: boolean
}

If I wanted to handle this state with a new Hooks-based approach, I could think of several options:

Option 1: using a Hook useState for each field

const [person, setPerson] = useState<Person>(null)
const [pet, setPet] = useState<Pet>(null)
const [loading, setLoading] = useState<boolean>(false)

This method has the disadvantage of low scalability and some of my real states have at least 15 fields, is unmanageable.

Option 2: Using a single setState for the entire object

const [state, setState] = useState<MyState>({
    person: null,
    pet: null,
    loading: false
})

This is the method that seems simplest to me, where I can simply do setState((prev) => {...prev, person: {username: 'New', email: '[email protected]'}}) or adapt it to any field modification. I can even update several fields at once.

Option 3: use a useReducer for each of the complex fields by passing a specific reducer for each one, use useState for the simple ones

const [person, dispatchPerson] = useReducer<Person>(personReducer)
const [pet, dispatchPet] = useReducer<Pet>(petReducer)
const [loading, setLoading] = useState<boolean>(false)

I find this one manageable, but I don't see the point of having to set up a reduce function with a multi-line switch, in addition to the tedious process of setting the dispatching types in Typescript for each reduce function when you could just use setState and be done with it.

Option 4: use one useReducer for the entire state

const [state, dispatch] = useReducer(generalReducer)

The main problem with this is the type of the reducer, think of 15 fields, where all the types and the structure of the information to update them are different. Specifying the types in Typescript does not scale or is unclear. There are several articles about this and none of them solve the problem in a clean way (example 1), or they are extremely simple and don't apply to the problem (example 2).

What would be the best way to handle this type of cases? Where the number of fields in the state is large and can have several levels of depth. Are there good practices or any official examples that represent these cases? The examples with a number field to handle a simple counter that bloggers or official documentation people like so much are not being very helpful.

Any light on the subject would be more than welcome! Thanks in advance and sorry about my English

like image 888
Genarito Avatar asked Apr 30 '20 19:04

Genarito


People also ask

How do I use a useReducer hook?

Syntax. The useReducer Hook accepts two arguments. The reducer function contains your custom state logic and the initialState can be a simple value but generally will contain an object. The useReducer Hook returns the current state and a dispatch method.

When should I use useReducer?

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

How do you handle complex state management in React?

The most-used hook in React is useState. We can use the useState hook inside a functional component and that will make our component associated with that state in particular. The useState hook is a simple function that we can pass an initial value.

What is the purpose of the useReducer hook?

The useReducer Hook is used to store and update states, just like the useState Hook. It accepts a reducer function as its first parameter and the initial state as the second.


1 Answers

I think your observations are spot on.

I think you should use Option 1 for simple state (e.g. you have only a few items to keep track of), and Option 2 for complex state (lots of items, or nested items).

Options 1 and 2 are also the most readable and declarative.

Option #2 is talked about here: https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

useReducer is more useful when you have multiple types of actions. If you're just updating state (one type of action), then actions are overkill. Also, useReducer is useful if you're performing calculations or transformations based on previous state (not just replacing part of state). If you're familiar with Redux, useReducer is a simplified version of Redux principles.

like image 152
stevenkkim Avatar answered Sep 20 '22 14:09

stevenkkim