DEV Community

Cover image for Discuss: React Hooks and Life After the Container-Component Pattern
Jen Chan
Jen Chan

Posted on • Updated on

Discuss: React Hooks and Life After the Container-Component Pattern

I'm late to the party; I've been working with React for roughly 4 months and trying to build a project with Typescript and Redux. This past July, it seems like the core React team announced hooks and selector functions was the way to go.

Just so we're all on the same page with what I'm talking about, this LogRocket post describes the container component pattern as:

"...containers are the components connected to the Redux store using the `connect()` method. These containers get some part of global state as props ...and a dispatch method to initiate changes in global state."

I have been watching those Dan Abramov tutorials on lifting state and Egghead's React-Redux over and over.

At my last workplace everything was written in the container component pattern with connect() and React Redux. In an attempt to prove I can consolidate what I learned and couldn't learn, I'm trying to build a front end oriented expense splitter app (WIP, and any comments are welcome!).

Welcome to Hell when I try to get the value out of an input:
because the property value is buried 2 layers deep, nested in an array of objects.

handleItemChange = (event: any) => {
    const { parentNode, value, name } = event.target;

    this.setState((prevState: State, props: Props) => {
      return {
        items: items.map(item => {
          if (parentNode.id === item.id) {
            if (name === 'name') {
              item.name = value;
              return {
                ...item,
                name: prevState.inputValue
              }
            }
            if (name === 'cost') {
              item.cost = value;
              return {
                ...item,
                cost: Number(prevState.inputValue)
              }
            }
          }
        }
        )
      }
    })
  }
Enter fullscreen mode Exit fullscreen mode

I've heard that the above method is not optimal because:

  • this.setState() only works on changing the first level of the state object.
  • inline action methods like onHandleChange() are taking up a lot of lines
  • more experienced devs prefer to use a state immutability lib such as Immer.

In theory, here are some cases for hooks:

  • hooks gives you direct access to the state from wherever in the app
  • hooks allow state to be used in functional components so that business logic isn't limited to class components.
  • useEffect will replace lifecycle methods such as componentDidMount and componentWillUnmount, in short, saving lines of code and redundancy in having to write the "cleanup" code...

So, now that I've tried lifting state the hard way, how should I go about refactoring for the better? Where do I start?

Context API with useState()?

To keep using connect with useSelector, as demonstrated in this YouTube tutorial?

Write my own reusable hooks?

Fellow devs, I'd like to know:

  • Have you switched over to hooks? Why or why not?
  • How do you typically deal with updating a property of the state object that's nested several levels deep?
  • Following from the previous question: how do you manage shared state?
  • Let's say you are building an app for a pizza shop. Your inventory is updated by one reducer, and the user info is handled by a different reducer. What would you do when a property like updating or cancelling a user's order requires both states? (In Typescript, I would imagine I would create a type OrderState that includes both... but isn't that duplicating a lot of data that's already in the App state?)

Recommended Reading
Presentational-Container pattern
Making Sense of React Hooks
Rules of Hooks
Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns

Top comments (3)

Collapse
 
sabbin profile image
Sabin Pandelovitch • Edited

Hello Jen, I've recently did some refactoring on one of my apps to using hooks. I'll try to answer some of your questions. Take into consideration I don't use typeScript.

Have you switched over to hooks? Why or why not?

I have switched to hooks because I find the code to be more readable. Also to remove the higher order components and layers over layers which the Redux connect and withStyles from material-ui had over the previous version.

When I would open the debug for components I would always have the WithStyles Connect(where was the case) components over my components, it was kind of hard to. (I know there is a search I used...)

The useSlector and useDispatch hooks work like a charm without having to use a connect function. For the MaterialUI they have useStyles hook also

The life cycles are gone thanks for that also useEffect hook dose a pretty good job of you understand how to use it.

The only thing that I encountered an issue with is the shouldComponentUpdate implementation in hooks, I am still trying to get something to work in that matter...

Comparing the hooks and non hooks versions I can say that the hooks is more readable more lightweight and much easier to backtrace and debug.

How do you typically deal with updating a property of the state object that's nested several levels deep?

There are 2 scenarios here from my point:

  1. Component state
  2. App state - Redux in my case also

Component state:

Let's say you have a form with a state which you send a request or something. The form has some inputs and some other components, like an autocomplete which is a component also, with another component in it and so on. If the N level of component controls only the state of the form and nothing else in the app, then I pass the handlers down via props.

For the App state:

Take the example above and say that when we submit the form above we need to update the store. Then we dispatch and update action from the form in the onSubmit.

Following from the previous question: how do you manage shared state?

Actually the question for me was how do I manage the data flow between async API calls and multiple actions within the states.

For that I use redux-saga

The one thing I love about redux-saga is that I can control the actions, the outcome of async actions and actually have a precise control over the update of the store and dispatching actions.

To answer your case study. With the pizza shop. (Hope I understood correctly)

Assume that the above form, from the previous question, is a pizza order and on submit you have 2 states to update in the store User and Inventory. Update the user orders and remove one item from the inventory.

  1. onSubmit dispatch and action orderPizza(({ type: 'ORDER_PIZZA_REQUEST' , payload: formState}))

  2. A ReduxSaga would have a takeLatest effect on the ORDER_PIZZA_REQUEST which would have a generator function to handle the effects

  3. call an API to add the order.

  4. Assume we don't have an error from the API and we have an orderId from the response, we would use the put effect which actually dispatches an action, to dispatch 2 actions one INVETORY_REMOVE_ITEM with the pizzaId and one USER_UPDATE_ORDERS whit the orderId

  5. In the userReducer handle the USER_UPDATE_ORDERS and in the invetoryReducer handle the INVENTORY_REMOVE_ITEM

Collapse
 
jenc profile image
Jen Chan • Edited

Hi Sabin, thank you so much for your thorough response; I thought that my long rambling post totally lost and bored people. And yes you pulled the question out of my mouth:

" how do I manage the data flow between async API calls and multiple actions within the states?"

From component to app state, yes. I realize after re-reading your answer a few times, that my initial confusion was due to an assumption about architecture. I had not thought about async actions and was conflating data types with component state, thinking each data type required a state and a corresponding reducer, (i.e. UserState, ItemState for each pizza, InventoryState, OrderState), which is quite superfluous. That dogpiled into an assumption that in order for an action to update multiple states, (instead of async actions like you suggested), that reducers would receive multiple state types as params (Just imagine the state object being returned with mismatching and redundant info!)

I also realize I had not looked into how hooks are referred to in reducers; that should close the loop :D

This is all to say, thank you for walking me through the example with such clarity.

Collapse
 
arntj profile image
Arnt Joakim Wrålsen

I've started using React Hooks and find that they let you write better and cleaner code. The downside is that they are very different from the "traditional" approach in React and it takes some time to get your head around them. This can be an issue in a team where many devs are used to the "traditional" way of developing React components and don't really have the time or energy to re-learn everything they know about React.