0

Problem Statement :

I am trying to setup a react component that will make an API call whenever a value is selected from the select box. I tried to make that happen in the useEffect hook but I am getting errors based on the rule of hooks that we can not call any hook inside a callback. Can you please tell me how can I fix this issue and do the required API call on any of the user Input.

I am looking over the pointers that can help me prevent this error and at the same time make an API call to the backend to fetch the records

Here is my code :

Component


const component: React.FC<ComponentProps> = () => {
  const { user } = useAppSelector((state) => state.auth);
  const periods = getPeriodNames();

  const [selectedPeriod, setSelectedPeriod] = React.useState(periods[0]);
  const [records, setRecords] = React.useState([]);
  const [columns, setColumns] = React.useState<any>();

  React.useEffect(() => {
    const [request] = React.useState<Request>({ // Throwing error:  React Hook "React.useState" cannot be called inside a callback.
      requester: user.alias,
      accountingMonth: selectedPeriod,
      limit: 300,
    });
    const { data, error, isLoading, isSuccess, isError } =
      useQuery(request); // Throwing error : React Hook "useQuery" cannot be called inside a callback.
    setRecords(data?.value);
  }, [selectedPeriod, user.alias]);

  const onPeriodSelect = (detail: SelectProps.ChangeDetail) => {
    setSelectedPeriod(selectedOption);
  };

  React.useEffect(() => {
    if (records) {
      // do something
    }
  }, [records]);

  return (
    <>
      <Select
        selectedOption={selectedPeriod}
        onChange={({ detail }) => onPeriodSelect(detail)}
        options={periods}
        selectedAriaLabel="Selected"
      />
    </>
  );
};

Setup to make an API Call


export const dynamicBaseQuery: BaseQueryFn<
  string | FetchArgs,
  unknown,
  FetchBaseQueryError
> = async (args, api, extraOptions) => {
  const { mainApiUrl } = (api.getState() as RootState).settings.endpoints;
  const rawBaseQuery = fetchBaseQuery({
    baseUrl: mainApiUrl,
    prepareHeaders: (headers, { getState }) => {
      // Use getState to pull the jwtToken and pass it in the headers to the api endpoint.
      const { jwtToken } = (getState() as RootState).auth;
      headers.set("authorization", jwtToken);

      return headers;
    },
  });
  return rawBaseQuery(args, api, extraOptions);
};

export const mainApi = createApi({
  reducerPath: "mainApi",
  baseQuery: dynamicBaseQuery,
  endpoints: () => ({}),
});

const useQuery = mainApi.injectEndpoints({
  endpoints: (builder) => ({
    query: builder.query<response, request>({
      query: (request?: request) => ({
        url: "/test_url",
        body: request,
        method: "POST",
      }),
    }),
  }),
  overrideExisting: false,
});

Any help would be really appreciated. Thanks

Diksha Goyal
  • 159
  • 2
  • 13
  • Does this answer your question? [Can I set state inside a useEffect hook](https://stackoverflow.com/questions/53715465/can-i-set-state-inside-a-useeffect-hook) – MORÈ May 02 '22 at 10:34
  • @MORÈ No it doesnot, actually I am aware that this is not possible, I am looking for a solution over how can I do the required API call, as I have tried everything that I could but go no luck. Even some hints/pointer over how to handle this situation would be helpful. Thanks – Diksha Goyal May 02 '22 at 10:37

2 Answers2

1

As the error tells, you should move your custom hook useQuery out of useEffect

You can add it on top of your component instead like below

const component: React.FC<ComponentProps> = () => {
  const { user } = useAppSelector((state) => state.auth);
  const [request, setRequest] = React.useState<Request | undefined>();
  const periods = getPeriodNames();
  const { data, error, isLoading, isSuccess, isError } =
      useQuery(request); //when component get re-rendered, and request state is there, it will fetch data


  const [selectedPeriod, setSelectedPeriod] = React.useState(periods[0]);
  const [records, setRecords] = React.useState([]);
  const [columns, setColumns] = React.useState<any>();

  //fetched successfully
  React.useEffect(() => {
    if(data) {
       setRecords(data.value);
    }
  }, [data])

  React.useEffect(() => {
    setRequest({
      requester: user.alias,
      accountingMonth: selectedPeriod,
      limit: 300,
    })
  }, [selectedPeriod, user.alias]);

  const onPeriodSelect = (detail: SelectProps.ChangeDetail) => {
    setSelectedPeriod(selectedOption);
  };

  React.useEffect(() => {
    if (records) {
      // do something
    }
  }, [records]);

  return (
    <>
      <Select
        selectedOption={selectedPeriod}
        onChange={({ detail }) => onPeriodSelect(detail)}
        options={periods}
        selectedAriaLabel="Selected"
      />
    </>
  );
};
Nick Vu
  • 8,514
  • 1
  • 14
  • 25
  • Thanks, it worked, but with the use of this approach, the UI is now lagging a lot I believe that is happening due to re-render. What is happening is, on an API call I am displaying the data into a table, now when the user is changing the value of the Select box, there is a delay of 1-2s where no attributes loading, etc are updated and then suddenly new data is displayed. Is there any way to fix it more efficiently? – Diksha Goyal May 02 '22 at 11:06
  • loading means data is being fetched. You should add another loading UI (based on `isLoading` which is from `useQuery`) to indicate to users that `we're fetching data, please wait...` @DikshaGoyal – Nick Vu May 02 '22 at 11:12
  • Yeah, actually, now with this change, the value of isLoading is always false, is there something that I might be missing in between? isLoading is true only once when the component renders for the first time. – Diksha Goyal May 02 '22 at 11:22
  • I think you need to check how you have set up `useQuery`. `isLoading` is controlled by `useQuery`, we don't know how they construct API calls from that. In this case, perhaps we need to deep dive into the document to figure out how it works... @DikshaGoyal – Nick Vu May 02 '22 at 11:32
  • Okay, Got it. Thanks Nick for the help! :) – Diksha Goyal May 02 '22 at 11:34
  • You're welcome!~ :D @DikshaGoyal – Nick Vu May 02 '22 at 11:35
0

You can put your API call inside a callback and call it inside your selectbox handler.

example:

const apiCall = (item) => {
  // api call logic
}

const handleSelectBox = (selectedItem)=> {
  apiCall(selectedItem)
}
fmsthird
  • 1,391
  • 2
  • 14
  • 31