Next / React JS infinite render component onClick

Viewed 173

New to react/next, sorry if this is obvious, i tried searching and tried the solutions to similar problems but i think im missing something or not understanding correctly. I am aware that loadStats() gets infinitely called because it's called inside of the render function, im just not sure how to solve it...

I tried onClick={loadStats(stats.player).bind(stats.player)} it just errors bind() is not a function... heres the component.

import React, { useEffect, useState } from "react";
import Teams from "./Teams";

const SquadMembers = ({ squadz }) => {
  const [teams, setTeams] = useState([]);

  const loadStats = async (player) => {
    const req = await fetch(`/api/player/${player}`);
    const json = await req.json();
    setTeams(json);
  };

  if (!squadz.teamStats) {
    return null;
  }
  let member = squadz.playerStats;
  return member.map((player, index) => (
    <React.Fragment key={index}>
      <br />{" "}
      <div>
        <br />
        {player.map((stats) => (
          <React.Fragment key={stats.player}>
            <div class="flex-child">
              <button value={stats.player} onClick={loadStats(stats.player)}>
                {stats.player}
              </button>
              <p>{stats.role}</p>
              <p>{stats.win_rate}</p>
              <p>{stats.matches}</p>
              <p>{stats.total_battles} Total Battles</p>
              <p>{stats.score} / Points Earned</p>
            </div>
          </React.Fragment>
        ))}
      </div>
      <br />
      <Teams teams={teams} />
    </React.Fragment>
  ));
};

export default SquadMembers;
3 Answers

your problem is

 onClick={loadStats(stats.player)}>

change it to:

 onClick={() => loadStats(stats.player)}>

The problem is that when you can the function with brackets () it's called immediately instead of binding the function to the onClick event.

So, you don't need to use brackets if you want to pass the function to another code so it can call the function. That how event handlers work. this.props.onClick should be called when the click event happens (which is some time in the future), not when the component is rendered.

To avoiding that you should bind the function to the click event like this:

onClick={loadStats}

or you can change the existing code with

onClick={() => loadStats(stats.player)}

The problem is how you are attaching a listener to onClick onClick={loadStats(stats.player)}>, you are invoking the function not attaching and inside the function loadStatus(stats.player) you are calling a state updating function setTeams(json); which again triggers the re-render of component and thats why it's causing the infinite loop.

to solve this you can use

  1. bind() (for class-based component)

onClick={this.loadStats.bind(this,stats.player)}

  1. Arrow function (in your case)

onClick={() => loadStats(stats.player)}

but from a performance point of view, if your component already contains a logic which can trigger render frequently and if you are passing props like this (for another component) then it will add overhead and again trigger render because both .bind() and ()=>{} approach returns a new function for each new render so every time treated as new props.

In such case, you can extract that part into a sub-component then pass the handler

 <Mybutton value={stats.player} onMyClick={loadStats}>
                {stats.player}
  </MyButton>

and component be like

const Mybutton = (props) =>{
 const handleClick = () => {
    this.props.onMyClick(this.props.value);
  }

    return (
     <button value={props.value} onClick={handleClick}>
                {props.children}
      </button>
    );
 
}

If you pass handler as props like above, then sub-component re-render only when the props change because the handler reference now never changes.:

Related