-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Infinite loop when using Suspense and key with multiple arguments, useMemo and useRef don't help #345
Comments
I think using objects inside the key array is not supported, except you create the object only one time, outside the component. This is because useMemo doesn't really guarantee your memoized object will be created only one time (React may ditch the memoized value at anytime) |
Thank you for letting me know this, 😀 I just learned. One of the solutions is using useMemoOne. Can As React official document says:
source: https://reactjs.org/docs/hooks-reference.html#usememo There are some discussions: |
After my multiple attempts, I found this problem don't caused by that React ditches the memoized value. Create params using
|
SolutionFor static paramsFor static params, create it outside component:const params = { a: 1, b: 2 };
function Profile({ url }) {
console.count("render");
const { data } = useSWR([url, params]);
return <div>Content: {data}</div>;
} For dynamic params(depends on other data)1. Lifting params up to the components which are outside the
|
So request again, can And it can solve this problem, don't cause inifite loop like:
|
Amazing investigation!
Yeah I think states from I think generally we have 2 solutions: SerializationI’d go this way: useSWR(JSON.stringify(params), () => fetcher(params)) Of course you can use any serialization lib here. And here we don’t see Global MemorizationYou can use global memorization as well, but I won’t recommend this way because it involves an extra mental layer and will possibly cause a memory leaking. |
Thanks! Serialization is a good way to solve it, but it still make some boilerplate code, and in many case, we will encapsulates fetcher outside, so we need to import files every time, we hope to configure it only once in import customFetcher from '@/utils/customFetcher'
function Test({ filter }) {
const params = useMemo(() => ({ ...filter, page: 1, pageSize: 10 }), [filter]);
useSWR(JSON.stringify(params), () => customFetcher('goods', params))
} I understand this is a trade-off, also agree that global memory is very bad, there are no perfect way. I think it's better if This can simplify the usage to: function Test({ filter }) {
useSWR(['goods', { ...filter, page: 1, pageSize: 10 }])
}
export defult App() {} And just configure import customFetcher from "@/utils/customFetcher";
import deepDiff from "deep-diff";
<SWRConfig
value={{
suspense: true,
keyDiffer: (prevKey, currentKey) =>
typeof prevKey === "string" && typeof currentKey === "string"
? prevKey === currentKey
: deepDiff(prevKey, currentKey),
fetcher: (...args) => customFetcher(...args)
}}
>
<ErrorBoundary>
<Suspense fallback={<div>loading...</div>}>
<Test filter={{ keyword: "shoe" }} />
</Suspense>
</ErrorBoundary>
</SWRConfig>; Of course it's better if React can come up with a solution. I will ask this question in React's issue when I have time. |
Isn't this the same scenario explained in Multiple Arguments? Although in this case they would pass shallow comparing. From my understanding(please correct me if I am wrong), the reason they are treated as different keys is because in the hash module, |
Share my current solution: First, create a helper function to import qs from 'qs';
export default function serialize(url: string, params?: object | any[]): string {
if (typeof params === 'object' || Array.isArray(params)) {
const matches = url.match(/^(.+?)(\?(.*))?$/);
const urlWithoutQueryString: string = (matches && matches[1]) || url;
const queryStringDataFromUrl: object = matches && matches[3] ? qs.parse(matches[3]) : {};
const queryString: string = qs.stringify(
{ ...queryStringDataFromUrl, ...params },
{ arrayFormat: 'indices' }
);
if (queryString) {
return `${urlWithoutQueryString}?${queryString}`;
}
return url;
}
return url;
} Then, import the helper function to import serialize from '../utils/serialize';
function Test({ filter }) {
const { data } = useSWR(serialize('/goods', { keyword: 'shoe', page: 1 }));
} |
Since SWR 1.1.0, there is built-in key serialization so you don’t have to handle this manually! 🎉 Check the release note here: https://github.com/vercel/swr/releases/tag/1.1.0 |
6 hours gone. Until I see your message. You saved my day. Thanks @sergiodxa |
@adnanfuat That is not true with the latest version, please try upgrading. |
A simplified reproduction: https://codesandbox.io/s/useswr-bug-muyru?file=/src/App.js
When click the button, look the console panel, you can see many "recompute params".
The text was updated successfully, but these errors were encountered: