React useEffect and useState based on async axios data?

Hey campers!
This is my connection manager page with React functional component.
Flow Diagram

Connection info from MongoDB => Multiple input => Multiple value(state) and onChange handler

I’m trying to fetch connection information then make the multiple inputs by Array.map in which it generate the state for onChanges (multiple onChange handler)

But i got issues… like below…
Issues

  1. axios is async, it is slower than rendering => it should be inside useEffect
  2. With fetched data from axios, I tried to use Array.map and setInputObj to make inputs and values at outside of useEffect, but i got infinite render (Even though I already used empty [ ] dependency to set it ‘mount only’ => I moved Array.map and setInputObj to inside of useEffect, it seems like fine
  3. But inside of useEffect, i can’t get inputObj which is states of inputs’ values
  4. After rendering, i can’t see any value of input, even though i already set the state of inputObj… (but the onChanges seems like work when i type something…)

Asking help
Plz let me know if those issues have some werid point and especailly i hope to solve 3 and 4…

Source

const Connect = ({ setIsConnected }) => {
  const history = useHistory();
  const [inputs, setInputs] = useState([]);
  const [inputObj, setInputObj] = useState({});
  const onChanges = ({ target: { id, value } }) => {
    console.log(id, value);
    setInputObj({ ...inputObj, [`${id}`]: value });
  };
  useEffect(() => {
    (async () => {
      const { data } = await axios("/showConn");
      /* axios will fetch below data from Mongodb
      [
        {_id: 1, alias: "TESTDB", host: "localhost", port: 1433, username: "sa" password: "*****"},
        {_id: 2, alias: "PRODDB", host: "localhost", port: 1434, username: "sa" password: "*****"}
      ]
      */
      setInputs(
        data.map((conn, i) => {
          return (
            <div key={`div__${i}`}>
              {Object.keys(conn).map((key) => {
                setInputObj((prevState) => {
                  return {
                    ...prevState,
                    [`${key}__${conn._id}`]: conn[key],
                  };
                });
                return (
                  <input
                    id={`${key}__${conn._id}`}
                    key={`${key}__${conn._id}`}
                    value={inputObj[`${key}__${conn._id}`]}
                    onChange={onChanges}
                  />
                );
              })}
            </div>
          );
        })
      );
    })();
  }, []);
  return (
    <>
      <form>
        {inputs}
        <button>Connect</button>
      </form>
    </>
  );
};

I think that if you open your console, react should tell you the problem.
If you’d were to run the exact same code in TypeScript it won’t even compile due to error

Argument of type '() => Promise<void>' is not assignable to parameter of type 'EffectCallback'.

Which, is a fancy way of saying that you are returning a promise, but useEffect wants either a cleanup function or nothing at all returned.

In case you are wondering why you are returning a Promise even you are not specifically doing so, it’s due to the fact that when you declare a function async you are effectively return a promise.

So in order to perform an async operation into useEffect you should call the async function in its body as:

useEffect(() => {
const fetchData = async () => {
  const result = await axios(...)
  setState(result) // set your state hook
};

fetchData() // run the async fn
}, [])

Hope it helps :slight_smile:

1 Like

Thank you for replying.
Few more questions

  1. I already glanced the console but it doesn’t have any error.
    It just did not render the value of input from axios data

  2. But my code works at least on Javascript… And i didn’t learn Typescript yet.
    Can you tell me what can be problem on Typescript?

  3. What is different between your code

useEffect(() => {
const fetchData = async () => {
  const result = await axios(...)
  setState(result) // set your state hook
};

fetchData() // run the async fn
}, [])

And anonymous aync function execution?

useEffect(() => {
(async () => {
  const result = await axios(...)
  setState(result) // set your state hook
})();
}, [])

Sorry if I put you off with Typescript, that was not my intention.
My idea was to tell you that in theory React should warn you of your function signature not be compliant with their API.

When you useEffect you should either return a cleanup function that react will call for you (for example to unsubscribe or kill any pending method) or nothing.

The first time I read your code I assumed you wrote:

useEffect(async () => ...)

That’s an error on my part.
So between an async IIFE and a function signature like the one I wrote in my example there should be no difference.

Is it still not working as intended?
If not, a demo would probably help in debugging :slight_smile:

Put a demo up if possible as @Marmiz says, but you should not be doing that complex rendering logic inside the setState function, it’s going to blow up.

You’re going to get crazy effects here because:

  1. you’re trying to render bits of your component as a side effect of the overall Connect function. The side effect is the result of fetch (a promise). You need a concrete value from that to be part of the output of the overall function, you can’t just merge it in as-is, it’s a different type of thing, as pointed out by @Marmiz. You may not use Typescript but what that error is stating is not TS-specific.
  2. The nodes you are trying to build in useEffect all, in turn, affect the internal state of the whole component. If a user interacts with an input that will change the relevant state (inputs, inputObj), which causes the component to rerender, which triggers useEffect again, &c.

Just fetch and set the data in useEffect, the rendering logic goes in the return value of the overall component function. This is not a React-specific thing. You have to have a single return value from a function. You can’t have a value where part of it is returned straightaway and part of it is not. This isn’t logical, it won’t work (though JS will really try make it not error at least). A function must resolve to one value.

1 Like