DEV Community

Daniel.xiao
Daniel.xiao

Posted on • Originally published at github.com

Is the React Hooks enough?

Here assumes that you are familiar with Hooks, the new feature on React.

Now, we are going to talk about whether the useXXX APIs currently available in React Hooks is sufficient and whether it can meet our daily development needs.

First, the most important: State

Daniel: Hey, useState is such a core API, isn't it specifically for dealing with state?

Yes, absolutely. Those of you who have used useState should know that the original this.setstate can now be replaced by the method setXXX in the return value of useState, as follows:

const [ count, setCount ] = useState(0);
Enter fullscreen mode Exit fullscreen mode

Let's make a simple comparison between class and Hooks:

# get state

class: this.state.count
Hooks: count

# set state

class: this.setState({count: 1})
Hooks: setCount(1)

Enter fullscreen mode Exit fullscreen mode

Daniel: That's easy. Anyone who's ever heard of React Hooks knows it. So that's what you're going to tell me?

Of course not. Did you find anything missing? Does the original this.setstate() has a second parameter? Is this second parameter a callback method? Is this method called after state update successful?

Daniel: That sounds like something you said, but callback is something I don't use very often. Can you tell me about the scenes used?

Of course, Such as city selector. When you select a province, you need to get the corresponding data of cities, and this moment callback will be used. Let's look at an example of setState:

class App extends React.Component {
  constructor() {
    this.state = {
      pId: 0
    };
  }

  onProvinceChanged(val) {
    this.setState({ pId: val }, () => {
      fetchCities(this.state.pId);
    });
  }

  ...
}
Enter fullscreen mode Exit fullscreen mode

Consider that without callback, it would be harder to do something else exactly after the state update

Daniel: So React Hooks can't fix it? I don't believe it.

Of course not. This requirement can be achieved by using useEffect. Let's take a look at how this can be done using the Hooks approach

function App() {
  const [pId, setPId] = useState(0);

  useEffect(
    function() {
      fetchCities(pId);
    },
    [pId]
  );

  function onProvinceChanged(val) {
    setPId(val);
  }
}
Enter fullscreen mode Exit fullscreen mode

Daniel: So what are you going to say?

If you look at the above codes, they looks like the event pattern, one is listening (useEffect listens for changes in pId and then executes the method) and one is firing the event(setPId).

The event pattern can act as a decoupling of the code, but it also means that the code is loose, with one side only responsible for triggering events and not caring about what happens next. But our requirements here are very clear, I chose the province, the next is definitely to load the city data, the logic of these two steps is related. So, of course, I hope to be close to the better, so that the code is more organized, read more smoothly, easy to understand.

Anyway, I feel that I still have to use the callback mode. It’s important to complete the task, but the readability and maintainability of the code are also very important.

Daniel: But didn't you say that useState doesn't provide callback? So what can we do now?

Of course, the official is not currently provided, but we can do it in the way of custom Hooks.

Now, we will use the third party open source library nice-hooks to meet our requirements.

Rewrite the above example in the way nice-hooks provide, as follows:

import { useStateCB } from 'nice-hooks';

function App() {
  const [pId, setPId] = useStateCB(0);

  function onProvinceChanged(val) {
    setPId(val, newPID => {
      fetchCities(newPID);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

You see, with the return of callback, Hooks are at least as good at handling state as this.setstate and are not left behind.

Ok, that's it for Hooks on handling state.

==== β˜• Suggest taking a break and looking into the distance for a few minutes ====

Next, talk about the life cycle.

As we all know, every component has its life cycle from birth to death. In React, often used are: componentDidMount, componentDidUpdate, componentWillUnmount.

Daniel: React Hooks have a lot of corresponding useXXX methods, right?

I think so too, but the reality is that we haven't found an official useXXX method for this. However, we can still implement these lifecycles with the official API currently available. So let's go through them one by one.

  • componentDidMount

This lifecycle method is executed after the component is mounted, and we can use useEffect to do this. How? Let's do an example

useEffect(() => {
  console.log('Do something, such as fetching data');    
}, [])
Enter fullscreen mode Exit fullscreen mode

Passing an empty array means that the dependency is invariant, so it only executes once after the first rendering of the component, which is equivalent to componentDidMount

  • componentWillUnmout

This function is executed when the component is about to be destroyed. Accordingly, we can still use useEffect to achieve this purpose. See the following example:

useEffect(() => {
  console.log('Do something, such as fetching data');
  return function() {
      console.log('Do something before destroyed')
  }
}, [])
Enter fullscreen mode Exit fullscreen mode

Since the destroy action is only executed once throughout the life cycle, we can add a return function to the first example that will be executed when the component is destroyed

  • componentDidUpdate

This function is executed whenever the props, state of the component changes, and you can still use useEffect to achieve this

useEffect(() => {
  console.log('Do something when props / state changes')  
})
Enter fullscreen mode Exit fullscreen mode

No dependency values are provided, so they are executed after each rendering, similar to componentDidUpdate.

But here is a little problem, that is, when it is initialized, it will be executed here too, that is, it will include DidMount. It needs to write some additional code to support it, but I'm not plan to expand it here.

We can also use useEffect for the purpose of watching a state or props change. So you will find that the life cycle is mixed in a bunch of useEffect code, not so straightforward.

Although useEffect can implement various lifecycle methods, it is still the problem, the readability and maintainability of the code are important. We can also use the nice-hooks. The usage is very simple and the code is clear at a glance.

useLifeCycle({
  didMount() {
    console.log('Do something, such as fetching data');
  },
  didUpdate() {
    console.log('Do something when props / state changes')   
  },
  willUnmount() {
    console.log('Do something before destroyed')  
  }
});
Enter fullscreen mode Exit fullscreen mode

In addition, the life cycle method of the class component has a small flaw, that is, when you need to destroy some things declared during initialization, such as event listeners, such as timers, registration and destruction logic is forcibly written in different places, easy to neglect and cause a bug, so useLifeCycle provides a didMountAndWillUnmount configuration to write the logic in pairs, as follows:

useLifeCycle({
    didMountAndUnmount: [
      {
          didMount() {
              console.log('register foo event)
          },
          willUnmount() {
              console.log('unregister foo event)
          }
      },
      {
          didMount() {
              console.log('register bar event)
          },
          willUnmount() {
              console.log('unregister bar event)
          }
      }
    ]
})
Enter fullscreen mode Exit fullscreen mode

Then the recommended practice is to write the paired logic in didMountAndWillUnmount, and others in didMount and willUnmount.

==== β˜• I suggest you take a break and listen a piece of music ====

Finally, let's talk about Instance Variables

When using Hooks to write components, because it is now a pure function component, you can't declare instance variables like class. The following variable is problematic.

function comp() {
    let name = 'daniel';
}
Enter fullscreen mode Exit fullscreen mode

Daniel: The variables declared in the function are normal. Is there a problem?

You may have modified the value of name somewhere, and expect that when you use the name variable, its value is the last modified value.

Unfortunately, it is counterproductive, because each time the component is re-rendered, the rendering function will be re-executed, and the variable will be re-initialized.

Daniel: Oh, it seems to be, now there is only one render function left, and it will be re-executed every time. What should we do?

We can use the official hook useRef, whose current attribute will always hold the last value, as follows:

function comp() {
  const nameRef = useRef('daniel');

  function someFn() {
    // get
    let name = nameRef.current;
    // set
    nameRef.current = 'sarah';
  }
}
Enter fullscreen mode Exit fullscreen mode

Once we have modified the value of the current property, the current value will remain the last modified value the next time we re-render, achieving the effect of the instance variable.

Daniel: I guess you will say something, such as the code is not very readable.

Yeah, you are right, the code looks unfriendly.

It is still recommended to use nice-hooks, its useInstanceVar hook, similar to useState, the difference is that setXXX does not cause re-rendering, just changed the value of the instance variable. The example is as follows:

function comp() {
  const [nameVar, setNameVar] = useInstanceVar('daniel');

  function someFn() {
    // get
    nameVar;
    // set
    setNameVar('sarah');
  }
}
Enter fullscreen mode Exit fullscreen mode

It is recommended to use Var as a suffix to distinguish state when declaring variable names, such as [ xxxVar, setXXXVar ]

==== Ending dividing line ====

All of the above are using nice-hooks, a third-party open source library to make React Hooks work better.

If you have some good suggestions, please feel free to submit some issues;

If you feel that it is useful to you, please add a star to this open source project.

Thanks for reading!

Top comments (0)