Swr: [Solved] Uncaught Error: Maximum update depth exceeded (using axios post)

Created on 21 Jul 2020  路  6Comments  路  Source: vercel/swr

Bug report

Description / Observed Behavior

Code keeps getting called repeatedly eventually ending in a Maximum update depth error.

Expected Behavior

Call should work as expected. I'm just trying to make a basic axios post request.

Repro Steps / Code Example

// App.tsx
import React from 'react';
import https from "https";
import axios from "axios";
import useSWR from "swr";

type myInput = {
  myInputData: string;
};

export function App() {
  const { data, error } = useSWR(
    [
      "/api/myEndpoint",
      {
        myInputData: "12345",
      },
    ],
    myFetcher
  );

 console.log("SWR response:", data);

  return <></>
}

const myFetcher = async (url: string, input: myInput) => {
  const agent = new https.Agent({
    // custom agent config
  });

  const response = await axios({
    method: "post",
    url: url,
    data: input,
    httpsAgent: agent,
  });

  return response.data;
};

Additional Context

SWR version: 0.2.3

Most helpful comment

Objects are not supported as part of a key, they are going to be regenerated on every render, instead try sending more values in the array or if possible move the definition of the object to outside the component (in your example is possible but most probably it will not when using it in an app)

All 6 comments

Objects are not supported as part of a key, they are going to be regenerated on every render, instead try sending more values in the array or if possible move the definition of the object to outside the component (in your example is possible but most probably it will not when using it in an app)

@sergiodxa Thank you! Looks like that was the issue. I passed the individual POST request input fields like this:

useSWR(
    [ "/api/myEndpoint", "12345", "abcd" ],
    myFetcher
);

Then I modified the fetcher to take those individual values and construct the object to send as input before making the axios call. However, this still doesn't ideally solve my use case. I'd like to write an axios POST based fetcher that can take an object and a url and just make a POST request to that url using the object as input.

Here's the fetcher I've written:

async function axiosPOST(urlEndpoint, inputData) {
  const agent = new https.Agent({
    // custom agent config
  });

  const response = await axios({
    method: "post",
    url: urlEndpoint,
    data: inputData,
    httpsAgent: agent,
  });

  return response.data;
}

Now if SWR doesn't support passing in objects then I'll need to write a separate fetcher for every API call that constructs the input object inside the fetcher before passing it to axios. Any way we can make a general fetcher like the above work with SWR?

For anyone who stumbles into the same error, I'll leave a minimal working code example here for axios post with typescript:

// Call.tsx
import React from "react";
import axios from "axios";
import useSWR from "swr";

type myInput = {
  id: string;
  name: string;
};

const inputData = { id: "12345", name: "abcd" };

export function Call() {
  const { data, error } = useSWR(
    ["/api/myEndpoint", inputData],
    axiosPostFetcher
  );

  if (data) console.log("SWR response:", data);
  if (error) console.log("SWR Error:", error);
  if (!data) console.log("SWR undefined");

  return <></>;
}

async function axiosPostFetcher(urlEndpoint: string, inputData: myInput) {
    const response = await axios({
      method: "post",
      url: urlEndpoint,
      data: inputData,
    });
    return response.data;
}

The problem with this approach is that your inputData for the post request always needs to be outside the component where useSWR() is getting called. This probably wouldn't work for most real use cases. So the only alternative seems to be passing in individual values to useSWR() and constructing the needed object inside the fetcher, which means you'll have to write a different fetcher for every different url endpoint, which seems counter-productive to me. It would be great if there was a way to make the above fetcher work without isolating the inputData outside the component where it's needed for the post call.

If you want to use objects with a single fetcher function instead of a custom one per useSWR call you can serialize it like this:

function fetcher(url: string, serializedParams: string) {
  const params = JSON.parse(serializedParams);
  // code here
}

useSWR(["/api", JSON.stringify({ token: userToken })], { fetcher })

Something like that

But I'm not sure how performant that could be, you are going to serialize and deserialize a lot.

If you don't enable Suspense on SWR, you may be able to also use React.useMemo to memoize the object and pass it directly, but this is also not 100% secure.

@sergiodxa Thanks a lot! The JSON serialize/deserialize example works perfectly. I'll mark the issue as resolved.

Documentation improvement suggestions for the SWR team:

  • The axios example in the docs gives no hint of how to use a post call. I found out from this comment that useSWR() passes arguments to the fetcher.
  • The arguments and object passing sections of the docs are probably relevant here. The axios example should perhaps mention somewhere that these sections should be referred for POST calls.
  • I looked at the axios typescript example but that seems too overkill for making a simple axios call. A "getting started" code, something like the one I posted above, is missing from documentation and would probably help beginners.

I just realized you are using SWR to do a POST, except when using GraphQL where you need to use POST to fetch data (usually), the recommended way to do a POST request is to don't use SWR, instead run your request in an effect or event handler, SWR can and will run multiple times and a POST is usually used to create new resources, using them combined will cause unwanted side-effects where you are creating new resources even if you don't need them.

Was this page helpful?
0 / 5 - 0 ratings