隆Estamos en directo en Twitch!

隆Entra y participa!

Imagen de la etiqueta react

C贸mo arreglar el error "Warning: React has detected a change in the order of Hooks"

3 minutos de lectura驴Una errata? Edita el art铆culo

Cuando trabajas en un proyecto con React, ya sea con Next.js o create-react-app, puedes encontrarte que la consola te muestra la siguiente advertencia:

Warning: React has detected a change in the order of Hooks called by...
This will lead to bugs and errors if not fixed.
For more information, read the Rules of Hooks: https://reactjs.org/docs/hooks-rules.html

El error, que fue introducido en React 16.8.0, aunque es algo cr铆ptico, quiere decir que est谩s usando los hooks de forma condicional o que no se est谩 ejecutando siempre los mismos hooks, y en el mismo orden, despu茅s de que el componente se renderice.

Por ejemplo, mira este componente:

function App() {
  const [count, setCount] = useState(0)
  if (count === 0) return 'No count' // 鉂

  useEffect(() => {
    trackCount(count)
  }, [])

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

Parece inofensivo pero la l铆nea con la 鉂 es problem谩tica ya que cuando count sea 0, entonces la funci贸n no ejecutar谩 el useEffect y, por lo tanto los hooks que se usan no son exactamente los mismos siempre.

Lo correcto ser铆a dejar esta condici贸n despu茅s de todas las llamadas a los hooks.

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

  useEffect(() => {
    trackCount(count)
  }, [])

  if (count === 0) return 'No count' // 鉁
  return <h1>{count}</h1>
}

Tambi茅n puede ocurrir si directamente usas un hook en un condicional de la siguiente forma:

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

  if (count > 9) {  // 鉂
    useEffect(() => {
      localStorage.setItem('count', count)
    }, [count])
  }

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

Aqu铆, se ejecuta el useEffect solo cuando el count sea mayor de 9. Esto hace que no siempre se ejecuten el mismo n煤mero de hooks ni en el mismo orden. Para evitarlo, tenemos que mover el condicional para que est茅 dentro del efecto.

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

  useEffect(() => {
    if (count > 9) { // 鉁
      localStorage.setItem('count', count)
    }
  }, [count])

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

El error tambi茅n lo he visto a veces al usar useSWR o react-query, hooks que te ayudan a hacer fetching de datos en tu app. As铆 que ten en cuenta que esta regla tambi茅n aplica para custom hooks y hooks de terceros.

Por ejemplo, con useSWR podr铆as estar tentado a hacer esto:

const App = () => {
  const [user] = useUser()
  let content

  if (user) {  // 鉂
    const { data } = useSWR(`/api/user`, fetcher)
    content = data.content
  }
  
  return (
    <>
      {content
        ? <h1>{content}</h1>
        : <span>Loading...</span>
      }
    </>
  )
}

Pero esto tambi茅n estar铆a mal porque estamos usando de forma condicional el hook useSWR. En este caso lo mejor ser铆a pasar null o undefined a useSWR para evitar que haga una llamada si no tenemos usuario:

const App = () => {
  const [user] = useUser()
  const { data } = useSWR(user ? `/api/user` : null, fetcher) // 鉁

  return (
    <>
      {data
        ? <h1>{data}</h1>
        : <span>Loading...</span>
      }
    </>
  )
}

Existe un plugin de eslint que puede ayudarte a evitar este tipo de problemas. Se llama eslint-plugin-react-hooks y est谩 mantenido por el equipo core de React.js, de forma que siempre incorpora las 煤ltimas ventajas para que funcione con React.

Ten en cuenta que existen decenas de posibilidades a la hora de cometer este error que no se cubren en el art铆culo porque podr铆an ser inabarcables. Por ejemplo, que el uso del hook lo tengas dentro de una funci贸n y sea esa funci贸n la que se ejecuta de forma condicional…

Lo mejor es que intentes colocar la llamada a los hooks siempre en el nivel superior del cuerpo de la funci贸n y lo m谩s arriba posible, de forma que evites este tipo de problemas.

Comparte el art铆culo