Home Java Functional components with React Hooks.How are they better?

Functional components with React Hooks.How are they better?

by admin

Relatively recently, React.js version 16.8 came out, with which we now have hooks available.The concept of hooks allows us to write complete functional components using all of React’s features, and allows us to do it in many ways more conveniently than we did with classes.

Many people have taken the advent of hooks with criticism, and in this article I’d like to talk about some of the important benefits that functional components with hooks give us, and why we should switch to them.

I deliberately won’t go into the details of using hooks.It’s not very important for understanding the examples in this article, a general understanding of how React works is enough.If you want to read about this particular topic, there is information about hooks at documentation , and if this topic is interesting, I’ll write an article more about when, what, and how to use hooks correctly.

Hooks make reuse of code more convenient

Let’s introduce a component that renders a simple form.Something that will just render a few instances and allow us to edit them.

This is roughly how, if greatly simplified, this component would look like as a class of :

class Form extends React.Component {state= {// field valuesfields:{}, };render(){return (<form>{/* Render the form intents */}</form>);};}

Now let’s imagine that we want to automatically save field values as they change.I propose to omit the declarations of additional functions, like shallowEqual anddebounce

class Form extends React.Component {constructor(props) {super(props);this.saveToDraft = debounce(500, this.saveToDraft);};state = {// Field valuesfields: {}, // The data we need to save the draftdraft: {isSaving: false, lastSaved: null, }, };saveToDraft = (data) => {if (this.state.isSaving) {return;}this.setState({isSaving: true, });makeSomeAPICall().then(() => {this.setState({isSaving: false, lastSaved: new Date(), })});}componentDidUpdate(prevProps, prevState) {if (!shallowEqual(prevState.fields, this.state.fields)) {this.saveToDraft(this.state.fields);}}render() {return (<form>{/* Render information about when a draft was saved */}{/* Render the form's intents */}</form>);};}

Same example but with hooks :

const Form = () => {//State for form valuesconst [fields, setFields] = useState({});const [draftIsSaving, setDraftIsSaving] = useState(false);const [draftLastSaved, setDraftLastSaved] = useState(false);useEffect(() => {const id = setTimeout(() => {if (draftIsSaving) {return;}setDraftIsSaving(true);makeSomeAPICall().then(() => {setDraftIsSaving(false);{ { setDraftLastSaved(new Date());});}, 500);return () => clearTimeout(id;)}; [fields]);return (<form>{/* Render information about when a draft was saved */}{/* Render the form's intents */}</form>);}

As we can see, the difference is not very big yet. We have changed the state to a hook useState and we call saving to draft not in componentDidUpdate but after rendering the component with the hook useEffect

The difference I want to show here (there are others, I will talk about them below): we can take this code out and use it elsewhere :

// the useDrafthook could well be put in a separate fileconst useDraft = (fields) => {const [draftIsSaving, setDraftIsSaving] = useState(false);const [draftLastSaved, setDraftLastSaved] = useState(false);useEffect(() => {const id = setTimeout(() => {if (draftIsSaving) {return;}setDraftIsSaving(true);makeSomeAPICall().then(() => {setDraftIsSaving(false);setDraftLastSaved(new Date());});}, 500);return () => clearTimeout(id;)}; [fields]);return [draftIsSaving, draftLastSaved];}const Form = () => {//State for form valuesconst [fields, setFields] = useState({});const [draftIsSaving, draftLastSaved] = useDraft(fields);return (<form>{/* Render information about when a draft was saved */}{/* Render form inputs */}</form>);}

Now we can use the hook useDraft that we just wrote, in other components! This is of course a very simplified example, but reusing the same kind of functionality is a very useful feature.

Hooks allow you to write more intuitive code

Introduce a component (so far as a class) that, for example, displays the current chat window, a list of possible recipients and a form to send a message. Something like this :

class ChatApp extends React.Component {state = {currentChat: null, };handleSubmit = (MessageData) => { makeSomeAPICall(SEND_URL, MessageData).then(() => {alert('Chat message ${this.state.currentChat}sent`);});};render() {return (<Fragment><ChatsList changeChat={currentChat=> {this.setState({ currentChat });}}/><CurrentChat id={currentChat} /><MessageForm onSubmit={this.handleSubmit} /></Fragment>);};}

This is a very fictitious example, but it will do for demonstration purposes. Imagine these user actions :

  • Open chat 1
  • Send message (let’s imagine that it takes a long time to send a request)
  • Open chat 2
  • Get a message about successful sending :
  • "Message to chat 2 sent"

But the message was sent to chat 1, right? That’s what happened because the class method wasn’t working with the value that was there when it was sent, but with the value that was already there when the request was finished. This wouldn’t be a problem in such a simple case, but fixing this behavior firstly would require extra care and extra processing, and secondly could be a source of bugs.

In the case of the functional component the behavior is different :

const ChatApp = () => {const [currentChat, setCurrentChat] = useState(null);const handleSubmit = useCallback((messageData) => {makeSomeAPICall(SEND_URL, messageData).then(() => {alert('Chat message ${currentChat} sent`);});}, [currentChat]);render() {return (<Fragment><ChatsList changeChat={setCurrentChat} /><CurrentChat id={currentChat} /><MessageForm onSubmit={handleSubmit} /></Fragment>);};}

Imagine the same user actions :

  • Open chat 1
  • Send message (request takes a long time again)
  • Open chat 2
  • Get a message about successful sending :
  • "Message to chat 1 sent"

So what has changed? What has changed is that now for every renderer for which the different currentChat we create a new method. This lets us not think at all about whether anything will change in the future – we work with what we have now. Each rendering of a component locks in everything that relates to it

Hooks rid us of the cycle of life

This point strongly overlaps with the previous one. React is a library for declarative interface description. Declarative makes it a lot easier to write and maintain components, and allows us to think less about what would have to be done imperatively if we weren’t using React.

Despite this, when using classes, we are faced with the lifecycle of a component. Without going too deep, it looks like this :

  • Mounting a component
  • Updating a component (when changing state or props )
  • Dismounting a component

This seems convenient, but I’m convinced that it’s convenient purely because it’s familiar. This approach is not like React.

Instead, functional components with hooks allow us to write components thinking not about lifecycle, but about synchronization We write a function so that its result unambiguously reflects the state of the interface depending on external parameters and internal state.

Hook useEffect which many people see as a direct replacement for componentDidMount , componentDidUpdate and so on, is actually meant for something else. When we use it, it’s as if we’re telling the reactor : "After you render this, please do these effects".

Here’s a good example of how the component works with the click counter from the great article about useEffect :

  • React: Tell me what to render with this condition.
  • Your component :
  • Here is the result of the rendering : <p> You clicked 0 times</p>
  • Also, please do this effect when you are done : () => { document.title = 'You clicked 0 times' }
  • React: Okay. Updating the interface. Hey braizer, I’m updating the DOM.
  • Browser : Great, I drew it.
  • React: Great, now I’ll call the effect I got from the component.
    • Runs () => { document.title = 'You clicked 0 times' }
    • Much more declarative, isn’t it?

      Results

      React Hooks allow us to get rid of some problems and make it easier to perceive and write component code. We just need to change the mental model we apply to them. Functional components are essentially interface functions from parameters. They describe things as they should be at any given time and help you not have to think about how to react to changes.

      Yes, sometimes you have to learn how to use them correctly but in the same way, we didn’t learn how to use components as classes right away.

      You may also like