Skip to content

fix: use full dequal so the default compare handles Map and Set#4269

Open
Profesor08 wants to merge 2 commits into
vercel:mainfrom
Profesor08:fix-map-set
Open

fix: use full dequal so the default compare handles Map and Set#4269
Profesor08 wants to merge 2 commits into
vercel:mainfrom
Profesor08:fix-map-set

Conversation

@Profesor08

@Profesor08 Profesor08 commented Jun 11, 2026

Copy link
Copy Markdown

Problem

The default compare option uses dequal/lite, which does not support Map and Set, it treats them as plain empty objects ({}). As a result, any two Maps (or Sets) are always considered deeply equal:

import { dequal } from 'dequal/lite';

dequal(new Map([['a', 1]]), new Map([['b', 2]])); // true (wrong)
dequal(new Set([1]), new Set([2])); // true (wrong)

Why this matters

Working with Set and Map is often much easier than with arrays: Map gives O(1) lookup by key (map.get(id)) instead of array.find(...), and Set gives built-in uniqueness and O(1) membership checks (set.has(x)) instead of array.includes(x). It's natural for fetchers and mutate calls to return data in these structures, so SWR's default comparison should handle them correctly rather than forcing users to fall back to arrays/plain objects or write a custom compare.

Solution

Switch the import in src/_internal/utils/config.ts from dequal/lite to the full dequal build, which deeply compares Map/Set contents (and typed arrays). No new dependency, dequal is already in use.

import { dequal } from 'dequal';

Tests

  • Unit (test/unit/compare.test.ts): the default compare with Maps/Sets of differing values, keys, and sizes; nested Map/Set; typed
    arrays; equal contents still compare as equal.
  • Integration (test/use-swr-local-mutation.test.tsx):
  • mutating with a Map/Set of different contents triggers a rerender
  • mutating with a deeply equal Map keeps the previous data reference

Current workaround is to patch package with npx patch-package swr

diff --git a/node_modules/swr/dist/_internal/config-context-12s-CCVTDPOP.mjs b/node_modules/swr/dist/_internal/config-context-12s-CCVTDPOP.mjs
index 6bc6621..7794e3f 100644
--- a/node_modules/swr/dist/_internal/config-context-12s-CCVTDPOP.mjs
+++ b/node_modules/swr/dist/_internal/config-context-12s-CCVTDPOP.mjs
@@ -1,7 +1,7 @@
 'use client';
 import React, { useEffect, useLayoutEffect, createContext, useContext, useMemo, useRef, createElement } from 'react';
 import * as revalidateEvents from './events.mjs';
-import { dequal } from 'dequal/lite';
+import { dequal } from 'dequal';
 
 // Global state used to deduplicate requests and store listeners
 const SWRGlobalState = new WeakMap();
diff --git a/node_modules/swr/dist/_internal/config-context-12s-DER5jwVW.js b/node_modules/swr/dist/_internal/config-context-12s-DER5jwVW.js
index d7c4d74..9a089d2 100644
--- a/node_modules/swr/dist/_internal/config-context-12s-DER5jwVW.js
+++ b/node_modules/swr/dist/_internal/config-context-12s-DER5jwVW.js
@@ -1,7 +1,7 @@
 'use client';
 var React = require('react');
 var revalidateEvents = require('./events.js');
-var lite = require('dequal/lite');
+var lite = require('dequal');
 
 function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
 
The default `compare` used `dequal/lite`, which treats Map and Set as
plain empty objects (`{}`). Any two Maps or Sets were always considered
equal, so updating data with a Map/Set of different contents never
triggered a rerender.

Switch to the full `dequal` build, which deeply compares Map/Set
contents (and typed arrays), and add:

- unit tests for the default compare with Map, Set, nested values
- integration tests verifying mutate with a changed Map/Set rerenders,
  and a deeply-equal Map keeps the previous data reference
@Profesor08 Profesor08 changed the title fix: use full dequal so the default compare handles Map and Set Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant