Technology

React – Hooks

Version 16.8.0 is the first release that brings stable support for Hooks . With this, simplifying the lives of programmers and curious people. Allowing the use of state and other library resources without writing a class.

Hooks Rules

  • Just call Hooks at the highest level . Do not call Hooks inside loops , conditionals or nested functions . React identifies a component’s hooks by the order in which they were called.
  • Just call Hooks in functional components . Don’t call Hooks in regular JavaScript functions . (There is only one other valid place to call Hooks — within your own custom Hooks.)

Check out the link to the eslint plugin so that the rules are automatically reviewed.

Types of Hooks

  • State hook – useState – allows you to read and store information in an easier and more practical way in the state, eliminating some class components and replacing them with functional components.
  • Effects hook – useEffect – used to execute functions that require or perform some effect on the component. Ex: mutations , subscriptions , timers and logging . It has the same effect that componentDidMount and componentDidUpdate have on classes.
  • Custom hook – Create your own hooks and extract a component’s logic into reusable functions.
  • Other Hooks – Native hooks with specific functions.

Existing Hooks can be classified as basic and additional. See the list below:

Basic hooks:

Additional hooks:

State hook

Let’s look at the state hook. Below we will see an example:

import React, { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      Você clicou {count} vezes!
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

The hook in this case is useState . It is receiving an initial state and returns an array with two values. The first value is the current state and the second is a function to update this state. useState does not have identical functionality to setState that is used in classes. When an object is passed to setState , it combines the value we are passing with the old one. In useState , the entire state of the hook will be changed, but we have the same effect using the spread operator . Ex: useState({ …oldState, …newState }); .

Example of an object in the initial state:

function Counter() {
  const [state, setState] = useState({ nome: '', idade: 0  })
  ...
  )
}

Also, we can call our useState hook more than once in our component.

function Counter() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)
  ...
  )
}

Effect hook

Now let’s talk about the useEffect hook . It allows your function-shaped component to have access to lifecycle methods without having to refactor your component into a class. Below is an example:

import React, { useState, useEffect } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    window.document.title = `Você clicou ${count} vezes!`
  })

  return (
    <div>
      Você clicou {count} vezes!
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
)}

The title will change according to the component state update. The useEffect in this context would be the same as the ComponentDidMount and also the ComponentDidUpdate . The passed function will be called both when the component is mounted and when it is updated.

useEffect helps you when unmounting resources, just like you would with ComponentWillUnmount .

function Example() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);

  useEffect(() => {
    const mouseMove = e => {
      setX(e.screenX)
      setY(e.screenY)
    }

    document.addEventListener('mousemove', mouseMove);
    return () => document.removeEventListener('mousemove', mouseMove);
  })

  return <div>O Mouse esta no {x}, {y}</div>;
}

Above we have the mousemove event configured to change the state of the component according to the mouse movement and when the component is dismounted the removeEventListener will be run.

useEffect

 It will also be called when it is detected that useEffect needs to be run again, that is, on each render. With each change in the component’s state, our event is being removed and added again. 

Now we don’t want that and we need the event to be added on mount and unmount.

Let’s use the second argument that useEffect receives, which is a list of values ​​that must change for it to run again. Passing an empty list, it will run only when it is mounted and the cleaning function will only run when it is dismounted.

function Example() {
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  useEffect(() => {
    const mouseMove = e => {
      setX(e.clientX)
      setY(e.clientY)
    }

    document.addEventListener('mousemove', mouseMove);
    return () => document.removeEventListener('mousemove', mouseMove);
  }, []) // <-- lista vazia

  return <div> Mouse esta no {x}, {y}</div>;
}

Event listeners will only be called when we need them. The second parameter can be used to tell when our effect will run. Below is an example:

function Status(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => setIsOnline(status.isOnline)

    API.subscribeToUserStatus(props.user.id, handleStatusChange)

    return () => API.unsubscribeFromUserStatus(props.user.id, handleStatusChange)

  }, [props.user.id]) // apenas se desinscreve caso props.friend.id mude
}

When friend.id is changed, we will call unsubscribeFromUserStatus with the previous id and then call subscribeToUserStatus with the current id, this way we have consistency in cleaning resources in a simple way.

Custom Hook

Hooks are completely decoupled from components, which allows us to combine them to create new, more specific hooks and share logic between our components.

We will start with the example below:

import React, { useState, useEffect } from 'react';

function Status(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    API.subscribeToUSerStatus(props.user.id, handleStatusChange)

    return () => API.unsubscribeFromUSerStatus(props.user.id, handleStatusChange)
    }
  })

  if (isOnline === null) return 'Loading...';

  return isOnline ? 'Online' : 'Offline';
}

We will also need a list of contacts and display their respective status.

import React, { useState, useEffect } from 'react';

function UserListItem(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    ChatAPI.subscribeToUserStatus(props.user.id, handleStatusChange)
    return () =>  API.unsubscribeFromUserStatus(props.user.id, handleStatusChange)
  })

  return <li style={{ color: isOnline ? 'green' : 'black' }}>{props.user.name}</li>;
}

With this, we have a repetition of code. We will solve this by extracting the repeated logic into a custom hook.

import React, { useState, useEffect } from 'react';

function useUserStatus(userID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = status => status.isOnline

    API.subscribeToUserStatus(userID, handleStatusChange)
    return () =>  API.unsubscribeFromUserStatus(userID, handleStatusChange)
  })

  return isOnline;
}

Now the logic we had in our components is in a separate function (a pattern: that hooks have the prefix use ). Below is an example of usage:

function UserStatus(props) {
  const isOnline = useUserStatus(props.user.id)

  if (isOnline === null) return 'Loading...';

  return isOnline ? 'Online' : 'Offline';
}
function UserListItem(props) {
  const isOnline = useUserStatus(props.user.id)

  return <li style={{ color: isOnline ? 'green' : 'black' }}>{props.user.name}</li>;
}

Now we have simplified logic. It is also possible to create hooks to deal with external libraries:

import React, { useState, useEffect } from 'react';

const useObservable = (observable, initialValue) => {
  const [value, setValue] = useState(initialValue)
  useEffect(() => {
    const subscription = observable.subscribe({next: setValue})
    return () => subscription.unsubscribe()
  }, [observable])
  return value
}

Above, with each new event in the observable stream we have an update on the state. Below, an example using the RxJS library .

import React from 'react';
import { fromEvent } from 'rxjs';
import { map }  from 'rxjs/operators';
import { useObservable } from './observableHook';

const mouseTest = fromEvent(document, 'mousemove').pipe( map(e => [e.clientX, e.clientY]) );

const App = () => {
   const [x,y] = useObservable(mouseTest, [0,0]);

   return <div>Mouse x:{x} y:{y}</div>;
}

Final considerations

Not much has been said about Other Hooks , but we can see the list at the beginning of this article. More about Hooks can be seen in the official documentation and in upcoming articles.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *