简体   繁体   中英

Why is my setState causing an infinite loop?

I have some code that gets a list of users from a firebase database, randomizes them, and then returns one - however when I try to set the state to the randomly selected user I'm getting an infinite loop.

I've tried moving the setState() between where it is now and the randomizeUsers function, but get the same results

Code in question:


  const [state, setState] = useState({
    potentialParnter: null
  });

  useEffect(() => {
    generateRandomPotentialPartner();
    console.log(state.potentialParnter)
  })

  const getUsers = () => {
    const database = firebase.database()
    const users = database.ref('users');
    return new Promise((resolve, reject) => {
      try {
        users.on('value', function(snapshot) {
            const users = []
            snapshot.forEach(function(childSnapshot) {
              let user = childSnapshot.val();
              users.push(user)
            });
            resolve(users);
        });
       } catch(e) {
           reject(e);
       }
    })   
  }

  async function generateRandomPotentialPartner() {
    try {
      const users = await getUsers();
      const randomUser = randomizeUsers(users)
      setState({
        potentialParnter: randomUser
      })
    } catch (error) {
      console.log(error)
    }
  }

  const randomizeUsers = (users) => {
    const randomPotentialParnter = users[Math.floor((Math.random() * users.length))]
    return randomPotentialParnter
  }

  const potentialParnter = state.potentialParnter ? state.potentialParnter.name : 'loading'

It's because of your useEffect() hook. The useEffect() hook is triggered anytime the state or props of your component is updated. Sort of like componentDidUpdate() .

Since you are performing some logic that updates your state in your useEffect , this would just create an infinite loop.

You can avoid this by adding an empty array as the 2nd parameter to your useEffect() . Making it virtually the same as a componentDidMount() . Thus the logic inside of it will only run a single time.

  useEffect(() => {
    generateRandomPotentialPartner();
    console.log(state.potentialParnter)
  }, [])

It's because of useEffect : if you don't provide a second parameter this hook will run every time your component re-renders.

So what happens in your code is as follow :

  1. your component is rendered for the first time
  2. useEffect runs
  3. you get a random user and setState
  4. your components re-renders
  5. useEffect runs again
  6. ...

If you want it to run only when you component is mounted , give an empty array as second parameter :

  useEffect(() => {
    generateRandomPotentialPartner();
    console.log(state.potentialParnter)
  }, []);

You should add an empty dependency array to the useEffect to prevent it cycling:

useEffect(() => {
  generateRandomPotentialPartner();
}, []);

You may need another useEffect if you want to log the result of state when it changes:

useEffect(() => {
  console.log(state.potentialParnter);
}, [state]);

Change this

  const [state, setState] = useState({
    potentialParnter: null
  });

To this (so that we can check for its length later to use that to know whether to rerender)

  const [state, setState] = useState({
    potentialParnter: ""
  });

====================================

Then change this

  useEffect(() => {
    generateRandomPotentialPartner();
    console.log(state.potentialParnter)
  })

to this (now we are checking to see if its length has changed, then we can rerender, else we wont rerender)

  useEffect(() => {
    generateRandomPotentialPartner();
    console.log(state.potentialParnter)
  }, [state.potentialParnter.length])

=======================================

Then change this

try {
  const users = await getUsers();
  const randomUser = randomizeUsers(users)
  setState({
    potentialParnter: randomUser
  })
} catch (error) {
  console.log(error)
}

To this (this is assuming you want the generation to happen only once)

try {
  const users = await getUsers();
  const randomUser = randomizeUsers(users)

  if(state.potentialPartner.length == 0){
  setState({
    potentialParnter: randomUser
  })
}
} catch (error) {
  console.log(error)
}

I hope this works for you. My answer is based on the same principle in the explanation of the other answers so no need repeating the explanation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM