reactjsreact-hooksaxiosreact-state

useState issue with axios instance in React


I have the following React component, it works very weird! Everyhing inside useEffect hook is correct (console.log shows correct value of the instance), but After calling setApiInstance with the newly created axios instance, the apiInstance unexpectedly becomes a Promise object, which is quite unusual!

Codepen: https://codesandbox.io/p/sandbox/4j2dzs?file=%2Fsrc%2FApp.tsx%3A15%2C30

import React, { useEffect, useState } from "react";
import axios, { AxiosInstance } from "axios";

const MyComponent = ({ appConfig }: { appConfig: { host: string } }) => {
  const [apiInstance, setApiInstance] = useState<AxiosInstance | null>(null);

  useEffect(() => {
    const instance = axios.create({
      baseURL: appConfig.host,
    });

    console.log("instance", instance);
    setApiInstance(instance);
  }, [appConfig]);

  console.log("apiInstance", apiInstance);
  if (!apiInstance) return;

  return null;
};
export default function App() {
  return <MyComponent appConfig={{ host: "0.0.0.0" }} />;
}

But If I create the axios instance in useState initializer method, it works fine!

const [apiInstance, setApiInstance] = useState<AxiosInstance | null>(() => axios.create({
        baseURL: appConfig.host,
        params: { website_token: appConfig.websiteToken }
    }));

Could you help me find out what is going on?


Solution

  • This is because in React, the setState() function that you get back from useState() can accept a function which acts as a state setter function, ie:

    setState(currState => newState);
    

    In your case, the instance which axios.create() returns is a function, so React treats this as a state setter function when you pass it to setApiInstance().

    You can get around this by using the state setter function and returning your instance:

    setApiInstance(() => instance);
    

    however, I would question whether you need to store your instance in state here in the first place. Something like this is typically better stored in a ref, as your UI most likely won't need to utilise/render your instance:

    const apiInstanceRef = useRef<AxiosInstance>();
    useEffect(() => {
      const instance = axios.create({
        baseURL: appConfig.host,
      });
      apiInstanceRef.current = instance;
    }, [appConfig]);