Setting state for parent from within useEffect hook in child component causes an infinite loop
Tags: react
Published: 2019-11-24 20:47:28

That's exactly what I have found recently.
Let's say we have a parent and a child and we pass `setState` function to the child in order to it can set state for parent from within a `useEffect` hook inside child component. This scenario will cause an infinite loop no matter what you put in the dependencies' second argument array of `useEffect` hook. 
Let's say what in my opinion happens. `setState` causes the parent to re-render because we are updating its state. But this implies a render of the child. And I say render and not re-render because when parent re-renders, for `useEffect` hook is like rendering of the child was first render, and that's why no matter what you put on the dependencies array it will always execute its side effect, setting state for parent and initiating a new loop, that will continue forever.
So when you lift the state up in React.js you must take care not to call `setState` or `dispatch` (this applies as well to `useReducer`) inside a `useEffect` hook from within a child component.
Here, I show you the code:
```javascript
import React,{useState} from 'react'
import s from 'styled-components'
import {Ein} from './ein/ein'
import iState from './state'

export const App=()=>{
  const[state,setState]=useState(iState)

  console.log('render app')

  const Div=s.div`
  `

  const el=<Div><Ein state={state} setState={setState}/></Div>

  return el
}
``` 
Previous is `app` component that calls to a child component in order to render it, and passes to it the `setState` function. Now we look at the `ein` component definition:
```javascript
import React,{useEffect} from 'react'
import s from 'styled-components'

export const Ein=({state,setState})=>{
  const Div=s.div`
  `
  
  console.log('render ein',state.hey)

  useEffect(()=>{
    console.log('useEffect')
    setState({
      ...state,
      hey:true
    })
  },[])

  const el=<Div></Div>
  return el
}
```
Previous is `ein` component, the child component for `app` component. Do not pay too much attention to the details of the `state` object. It doesn't matter. The thing is we are setting the state for parent component from within a `useEffect` hook inside child component, and this will inevitably cause an infinite loop.
If we change the location of the `useEffect` hook and call it from the parent component instead of the child component, the infinite loop disappears.
```javascript
import React,{useState,useEffect} from 'react'
import s from 'styled-components'
import {Ein} from './ein/ein'
import iState from './state'

export const App=()=>{
  const[state,setState]=useState(iState)

  console.log('render app')

  const Div=s.div`
  `

  useEffect(()=>{
    console.log('useEffect')
    setState({
      ...state,
      hey:true
    })
  },[])

  const el=<Div><Ein state={state} setState={setState}/></Div>

  return el
}
```
and
```javascript
import React,{useEffect} from 'react'
import s from 'styled-components'

export const Ein=({state,setState})=>{
  const Div=s.div`
  `

  console.log('render ein',state.hey)

  const el=<Div></Div>
  return el
}
```
Now we don't have anymore an infinite loop.
That is even more clear if we use `useRef` to create a `var` where to store if it's the first render or not:
```javascript
import React,{useEffect,useRef,useState} from 'react'
import s from 'styled-components'

export const Ein=({state,setState})=>{
  const Div=s.div`
  `

  const [state2,setState2]=useState({count:0})

  console.log('render ein')

  const isFirstRender= useRef(true)

  useEffect(()=>{
    console.log('isFirstRender',isFirstRender.current)
    if(isFirstRender.current){
      isFirstRender.current=false
    }
    setState({
      ...state,
      hey:true
    })
  },[])

  const el=<Div></Div>
  return el
}
```
You see how we receive as prop in child component the `setState` function from parent and also declare a new `setState2` function local to the child component.
When we use the `setState` function from parent in the `useEffect` hook that's what we get in the console:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/pf8qkvtfvzkrew7gdwcp.png)
That is, we get an infinite loop because it always is the first render, while if we use the local `setState2` function as in here:
```javascript
import React,{useEffect,useRef,useState} from 'react'
import s from 'styled-components'

export const Ein=({state,setState})=>{
  const Div=s.div`
  `

  const [state2,setState2]=useState({count:0})

  console.log('render ein')

  const isFirstRender= useRef(true)

  useEffect(()=>{
    console.log('isFirstRender',isFirstRender.current)
    console.log('count',state2.count)
    if(isFirstRender.current){
      isFirstRender.current=false
    }
    setState2({
      ...state2,
      count:state2.count<5?state2.count+1:state2.count
    })
  },[state2.count])

  const el=<Div></Div>
  return el
}
```
we get this in the javascript console:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/4k9h8h3z52t89i3fksth.png)
As you can see we do not get anymore an infinite loop and `useEffect` works properly because it is not anymore the first render each time.
Thank you.