Search code examples
javascriptnode.jsreactjsmeteorreact-hooks

Meteor.call in for each loop runs in infinite loop


Meteor newbie here, I have list loop that does a meteor call to the meteor method. I need the results populated in another object with each list item as key and value as the meteor method response. After all the item related meteor calls are done returning responses, I want to print the completely updated object in the end

This is my following code and it runs in an infinite loop

  const [codeCoverage, setcodeCoverage] = useState({});
  projectList = ['proj1','proj2','proj3']
  projectList.map((project) => {
    Meteor.call(
      'sonarqb.getProjectStatus',
      { project },
      (error, resp) => {
        if (error) throw new Error(error);
        setCodeCoverage({...codeCoverage, [project] : val.actualValue })
      }
    )
  });

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

Meteor Method serving the above call

Meteor.methods({
  'sonarqb.getProjectStatus'({ project }) {

    const options = {
      method: 'GET',
      url: `https://sonarqube.com/sonar/api/qualitygates/project_status?projectKey=${project}`,
      headers: {
        Authorization:
          'Basic <auth str>',
      },
    };

    const future = new Future();

    request(options, function(error, response) {
      if (error) throw new Error(error);
      const sonarResponse = JSON.parse(response.body);
      future.return(sonarResponse);
    });
    return future.wait();
  },
});


Solution

  • Assuming this is a React function component, the issue is that your Meteor.calls happen every time the component renders, which includes every time you call setCodeCoverage, i.e., every time a call completes — hence an infinite loop.

    You need to wrap the Meteor.calls in a useEffect (or something), not have them at the top level of your component. If you want them to just happen once at the beginning of your component:

      const [codeCoverage, setcodeCoverage] = useState({});
      const projectList = ['proj1','proj2','proj3'];
      useEffect(() => {
        projectList.forEach((project) => {
          Meteor.call(
            'sonarqb.getProjectStatus',
            { project },
            (error, resp) => {
              if (error) throw new Error(error);
              setCodeCoverage((previous) =>
                {...previous, [project] : val.actualValue });
            }
          )
        })
      }, []); // no deps => run once at beginning
    
      useEffect(() => {
        console.log(codeCoverage);
      }, [codeCoverage]);