Search code examples
reactjsuse-effect

How to Handle State Being Re-Initialized on API Call Response?


I have the below functional component in a simple MERN application. My problem is the output state is reset to ["Let's run a test..."] when the API call returns, so I always only get an array of that and the last output, instead of an array of all outputs.

output state looks like: ["Let's run a test..."] on refresh the page, ["Running tests...] on testRunner click, and ["Let's run a test...", "test result"] on testRunner response. I'm expecting ["Running tests...", "test result 1", "test result 2", ...] on testRunner response. Is useEffect having some effect I'm not aware of? How should I be doing this?

import React, { useState, useEffect } from 'react';
import apis from '../api/index';
import Header from './Header';
import Output from './Output';

function App() {

  const [output, setOutput] = useState([]);

  const testRunner = async () => {

    setOutput(["Running tests..."]);

    for (const item of selectedItemTypes) {
      const payload = { 
        "itemPrefix": item,
        "testFunction": selectedTestFunctions
      };
      await apis.testRunner(payload).then(res => {
        setOutput([...output, `${item}: ${res.data.result}`]);
      });
    }
  }

  useEffect(() => {

    async function initialize() {
      setOutput(["Let's run a test..."]);
    }

    initialize();

  }, []);

  return (
    <div className="App">
      <Header 
        testRunner={testRunner}
      />
      <Container>
        <ChildContainer>
          <TitleDiv>Select Test Function(s)</TitleDiv>
          <TestPicker />
        </ChildContainer>
        <ChildContainer>
          <TitleDiv>Select Item Type(s)</TitleDiv>
          <ItemTypePicker />
        </ChildContainer>
        <ChildContainer>
          <TitleDiv>Output</TitleDiv>
          <Output
            output={output}
           />
        </ChildContainer>
      </Container>
    </div>
  );
}

export default App;

Solution

  • You need to pass the old output value to setOutput in order to get the result you want. So in your testRunner function you'll do something like this.

    await apis.testRunner(payload).then(res => {
        setOutput((output) => [...output, `${item}: ${res.data.result}`]);
    });
    

    React Docs