Random memo

React hooks in functional component

November 03, 2018

React’s useEffect hook in a functional component can be executed at lifecyles of class component’s componentDidMount, componentDidUpdate, and componentWillUnmount lifecycles.

By default, useEffect gets triggered when an update happens to the React component.

This includes when a component receives new props from it’s parent component or even when you change the state locally. Let’s see example below.

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const rootElement = document.getElementById("root")

const App = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setTimeout(() => {
      setCount(count + 1)
    }, 1000)
  })

  return <h1>Count: {count}</h1>
}

ReactDOM.render(<App />, rootElement)

In this case, count keeps increasing in every 1 second because the component’s mounting triggers useEffect, which updates the state change of count in one second and any state change triggers useEffect again and that updates state again and this cycle keeps going on.

That means useEffect hook gets executed at componentDidUpdate as well as componentDidMount.

If you want useEffect not triggered at componentDidUpdate for the state change of count, exclude it in the second parameter of useEffect, like so:

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const rootElement = document.getElementById("root")

const App = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setTimeout(() => {
      setCount(count + 1)
    }, 1000)
  }, [])

  return <h1>Count: {count}</h1>
}

ReactDOM.render(<App />, rootElement)

Here, empty array [] is second parameter of useEffect and count is excluded. Empty array means that nothing triggers execution of useEffect. Therefore, useEffect gets executed only at componentDidMount lifecycle.

If you want useEffect triggered at change of only count value, provide [count] as the second parameter.

If you want something like clear operation executed at componentWillUnmount lifecycle, return a clear function in your useEffect function, like so:

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const rootElement = document.getElementById("root")

const App = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setTimeout(() => {
      setCount(count + 1)
    }, 1000)

    return () => console.log("clear something before component unmount.")
  }, [])

  return <h1>Count: {count}</h1>
}

ReactDOM.render(<App />, rootElement)

In this case, the function () => console.log("clear something before component unmount.") will get executed before component unmounting.

useReducer hook

userReducer hook takes 2 parameters. First one is a function as form of:

(oldState, action) => newState

and second parameter is initial value of the state of the function.

The example code below might self explain.

function Counter() {
  const [count, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case "add":
        return state + 1
      case "subtract":
        return state - 1
      default:
        return state
    }
  }, 0)

  return (
    <div>
      <h1>Counter {count}</h1>
      <button onClick={() => dispatch({ type: "add" })}>Add</button>
      <button onClick={() => dispatch({ type: "subtract" })}>Subtract</button>
    </div>
  )
}

useLayoutEffect

useLayoutEffect is identical to useEffect, but it’s major key difference is that it gets triggered synchronously after all DOM mutation.

You only want to use this hook when you need to do any DOM changes directly, usually with useRef.

Read an article here

This hook is optimized to allow the engineer to make changes to a DOM node directly before the browser has a chance to paint.

custom hook

Simple todo app with custom useArray hook example.

import { useCallback, useMemo, useState } from "react"

export function useArray(initial) {
  const [value, setValue] = useState(initial)
  const add = useCallback(a => setValue(v => [...v, a]), [])
  const move = useCallback(
    (from, to) =>
      setValue(it => {
        const copy = it.slice()
        copy.splice(to < 0 ? copy.length + to : to, 0, copy.splice(from, 1)[0])
        return copy
      }),
    []
  )
  const clear = useCallback(() => setValue(() => []), [])
  const removeById = useCallback(
    // work only when value object with id field.
    id => setValue(arr => arr.filter(v => v && v.id !== id)),
    []
  )
  const removeIndex = useCallback(
    index =>
      setValue(v => {
        const copy = v.slice()
        copy.splice(index, 1)
        return copy
      }),
    []
  )
  return {
      value,
      setValue,
      add,
      move,
      clear,
      removeById,
      removeIndex,
    }
}

const App = () => {
  const todos = useArray(["hi there", "sup", "world"])

  return (
    <div style={{ padding: 20 }}>
      <h3>Todos</h3>
      <button onClick={() => todos.add(Math.random())}>add</button>
      <ul>
        {todos.value.map((todo, i) => (
          <li key={i}>
            {todo}
            <button type="button" onClick={() => todos.removeIndex(i)}>
              delete
            </button>
          </li>
        ))}
      </ul>
      <button type="button" onClick={todos.clear}>
        clear todos
      </button>
    </div>
  )
}

export default App

Typescript version of the original example can be found here


Sangche

Written by Sangche. Github