-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Bug report
Description / Observed Behavior
When using a custom compare function in the useSWR hook, the function is called repeatedly (depending on how many React components are using the hook), rather than only once after new data has been fetched. This leads to a performance bottleneck, especially when the custom comparison logic is complex, such as a deep comparison of large data arrays. The console.log statement within the compare function confirms this behavior, showing it being triggered an excessive number of times.
Expected Behavior
The compare function should be invoked only once per revalidation cycle, specifically after the fetcher has successfully returned new data. Its purpose is to determine whether the newly fetched data is different from the data currently in the cache, and thereby prevent unnecessary state updates and re-renders if the data is functionally the same.
Repro Steps / Code Example
const useUsers = () => {
const fetcher: Fetcher<IUser[], string> = getUsers;
const { data: users, ...rest } = useSWR("/users", fetcher, {
// Compare new fetched data with existing cached data
// Determine if the data has changed
compare(cachedData, newData) {
// compare should only be called once if new data was fetched
// but get's called countless times (depending on how many React components are using useUsers() hook)
console.log({ cachedData, newData });
// Custom compare logic
// shallow compare
if (!cachedData || !newData) return cachedData === newData;
if (cachedData.length !== newData.length) return false;
// Deep compare
const mapOld = new Map(cachedData.map((user) => [user._id, user]));
const mapNew = new Map(newData.map((user) => [user._id, user]));
for (const [_id, oldUser] of mapOld) {
const newUser = mapNew.get(_id);
if (!newUser || !areUsersEqual(oldUser, newUser)) return false;
}
return true;
},
});
return {
users,
...rest,
};
};
export default useUsers;
Additional Context
SWR version: 2.3.4
After I looked into the SWR source code in use-swr.ts, I found a section that appears to be the intended behavior:
finalState.data = compare(cacheData, newData) ? cacheData : newData
https://github.com/vercel/swr/blob/main/src/index/use-swr.ts#L483
but I do not fully understand the meaning of the following code and I am not sure if this might trigger the excessive amount of compare function calls.
const isEqual = (prev: State<Data, any>, current: State<Data, any>) => {
for (const _ in stateDependencies) {
const t = _ as keyof StateDependencies
if (t === 'data') {
if (!compare(prev[t], current[t])) {
if (!isUndefined(prev[t])) {
return false
}
if (!compare(returnedData, current[t])) {
return false
}
}
} else {
if (current[t] !== prev[t]) {
return false
}
}
}
return true
}
https://github.com/vercel/swr/blob/main/src/index/use-swr.ts#L154C1-L173C4
Am I missing something here or is this a faulty behavior?