Is the React Hooks enough?
Tags: react, hooks, javascript
Published: 2019-10-16 03:35:59

![](https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2369947519,91440372&fm=15&gp=0.jpg)

> 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);
```

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)

```

> 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);
    });
  }
  
  ...
}
```

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);
  }
}
```

> 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](https://github.com/daniel-dx/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);
    });
  }
}
```

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');    
}, [])
```

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')
  }
}, [])
```

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')  
})
```

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](https://github.com/daniel-dx/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')  
  }
});
```

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)
          }
      }
    ]
})
```

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';
}
```

> 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';
  }
}
```

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](https://github.com/daniel-dx/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');
  }
}
```

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](https://github.com/daniel-dx/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!