vbotio
vbotio

Reputation: 1674

Rendered more hooks than during the previous render using useEffect

I have a component with an array of objects, which among other things i am filtering based on a string. Problem is when I try to set the return of this filter to the local state, it's throwing errors that i am not quite understanding the reason.

import React, { useState, useEffect } from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag'
const ProductsGrid = () => {


const [productsList, setProductsList] = useState([]);
  const { loading, data } = useQuery(GET_PRODUCTS);
  if (loading) return <div><h4>bla bla bla</h4></div>
  const { merchants } = data;
  let filtered = [];
  merchants.map(merchant => {
    merchant.products.map(product => {
      if (product.name.includes('Polo')) {
        filtered.push(product);
      }
    })
  })
  console.log({ filtered });
}

This is printing the following: enter image description here

So, because I want this array in my state, I decided to do this: setProductsList(filtered); and what happened after inserting this line was this:

enter image description here

It started rendering multiple times. I assumed that, every time the state changes, it re-renders the component (correct me if im wrong). I don't know why it did it multiple times though.

So, I thought on using useEffect to achieve the expected behaviour here.

useEffect(() => {
console.log('useeffect', data);
if (data) {
  const { merchants } = data;
  console.log({merchants })
  let filtered = [];
  merchants.map(merchant => {
    merchant.products.map(product => {
      if (product.name.includes('Polo')) {
        filtered.push(product);
        // console.log({ filtered });
      }
    })
  })
  console.log({ filtered });
  setProductsList(filtered);
}

}, [data])

and the output was it: enter image description here

So, I am understanding what's going on here and what is this last error about. I assume my approaching is towards the right direction, using useEffect to run the function only once.

Upvotes: 1

Views: 6553

Answers (1)

backtick
backtick

Reputation: 2775

Your problem is due to the useEffect call occurring after the if (loading) condition, which returns early.

Calling hooks after a conditional return statement is illegal as it violates the guarantee that hooks are always called in exactly the same order on every render.

const { loading, data } = useQuery(GET_PRODUCTS);
const [productsList, setProductsList] = useState([]);
if (loading)
  return (
    <div>
      <h4>bla bla bla</h4>
    </div>
  );  // <-- Cannot use hooks after this point
useEffect(/* ... */)

To fix, move the useEffect call to be before the conditional.

Upvotes: 5

Related Questions