简体   繁体   中英

How do I prevent this endless re-render in react?

I'm building a React app, but I haven't quite got my head around the use of hooks yet.

My data is stored in the following format in Firestore:

userlists > key1 > users: [user1, user2, user3]
                 > date_created: date
                 > listname: "Work Colleagues"
          > key2 > users: [user1, user4, user5]
                 > date_created: date
                 > listname: "Badminton Friends"

(where user1, user2 etc are objects holding user data)

So, in EditUserlistPage I want to retrieve the userlist with key key1 , and render a UserList component to show it. The UserList component then renders an individual UserItem component for each user

EditUserlistPage is as follows ( key is passed in via props):

const EditUserlistPage = (props) => {

  // Get list key
  const listKey = props.key

  // Set up state variables
  const [userlist, setUserlist] = useState({})

  // Load list from db once component has mounted
  useEffect(() => {
    props.firebase.getListByKey(listKey).get().then(doc => {
        let userlist = doc.data()
        setUserList(userList)
    })
  }, [listKey, props.firebase]) 

  return (
    <div>
      <h1>Edit User List</h1>
      <UserList
        userlist={userList}
      />
    </div>
  )
}

export default withFirebase(EditUserlistPage)

The UserList component is:

const UserList = (props) => {

  // Get list
  const { userlist } = props
  
  // Set up state variable - users
  const [ users,  setUsers] = useState([])

  // Now get all users as objects
  let usersTemp = []

  for(let ii=0; ii<userlist.users.count; ii++) {

      const user = userlist.users[ii]
      
      const userItem = {
        id: user.index,
        name: user.firstname + user.surname
        ... // More things go here, but I don't think they're relevant
      }
      usersTemp.push(userItem)
    }
  }

  setUsers(usersTemp)

  return (
    <div className="userList">
      { // This will render a UserItem component}
    </div>
  )

}

export default UserList

Finally, props.firebase.getListByKey is:

getListByKey = (key) => this.firestore.collection('userlists').doc(key)

I'm getting an error and a warning.

Firstly, displayed on screen is: Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. In the console I can also see this error, and it says:

The above error occurred in the <UserList> component:
    in UserList (at EditUserlistPage/index.js:59)
    in div (at EditUserlistPage/index.js:54)
    in EditUserlistPage (at context.js:7)

This error goes away if I comment out the line in EditUserlistPage which renders the UserList component.

Secondly, I'm getting a warning in the console:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in EditUserlistPage (at context.js:7)

context.js is the Firebase Context, and is:

const FirebaseContext = React.createContext(null)

export const withFirebase = Component => props => (
  <FirebaseContext.Consumer>
    {firebase => <Component {...props} firebase={firebase} />} // Line 7
  </FirebaseContext.Consumer>
)
 
export default FirebaseContext

I've tried to read the React documentation on Hooks, and I've gathered that useEffect can caused infinite re-rendering if not implemented properly, but I can't figure out how to do it correctly.

The main problem is with setUsers(usersTemp) in the code of the UserList .

Whenever some local state is changed, the component re-renders. So, since you always re-set the users during the render, you trigger another render.

You could use a useEffect and only update the users when the userList changes

const UserList = (props) => {

  // Get list
  const {
    userlist
  } = props

  // Set up state variable - users
  const [users, setUsers] = useState([])

  useEffect(() => {
      // Now get all users as objects
      let usersTemp = []

      for (let ii = 0; ii < userlist.users.count; ii++) {
        const user = userlist.users[ii];
        const userItem = {
          id: user.index,
          name: user.firstname + user.surname
            ... // More things go here, but I don't think they're relevant
        }
        usersTemp.push(userItem)
      }
    }

    setUsers(usersTemp)
  }, [userlist])

return (
    <div className="userList">
      { // This will render a UserItem component}
    </div>
  )
}

export default UserList

When you're using functional components, your entire function will run again when your state updates. So in your UserList component, you're updating the state every time it renders, therefore triggering a new render.

To prevent that, use useEffect with no dependencies. That will cause the useEffect to only run once when the component mounts, very much like a componentDidMount.

const UserList = (props) => {

  // Get list
  const { userlist } = props
  
  // Set up state variable - users
  const [ users,  setUsers] = useState([])

  useEffect(() => {
    // Now get all users as objects
    let usersTemp = []

    for(let ii=0; ii<userlist.users.count; ii++) {

        const user = userlist.users[ii]
      
        const userItem = {
          id: user.index,
          name: user.firstname + user.surname
          ... // More things go here, but I don't think they're relevant
        }
        usersTemp.push(userItem)
      }
    }

    setUsers(usersTemp)
  },[])



  return (
    <div className="userList">
      { // This will render a UserItem component}
    </div>
  )

}

export default UserList

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