Skip to content
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

[v5] Rewrite shallow to support iterables #2427

Merged
merged 1 commit into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions src/vanilla/shallow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
export function shallow<T>(objA: T, objB: T) {
const isIterable = (obj: object): obj is Iterable<unknown> =>
Symbol.iterator in obj

const compareMapLike = (
iterableA: Iterable<[unknown, unknown]>,
iterableB: Iterable<[unknown, unknown]>,
) => {
const mapA = iterableA instanceof Map ? iterableA : new Map(iterableA)
const mapB = iterableB instanceof Map ? iterableB : new Map(iterableB)
if (mapA.size !== mapB.size) return false
for (const [key, value] of mapA) {
if (!Object.is(value, mapB.get(key))) {
return false
}
}
return true
}

export function shallow<T>(objA: T, objB: T): boolean {
if (Object.is(objA, objB)) {
return true
}
Expand All @@ -11,26 +29,30 @@ export function shallow<T>(objA: T, objB: T) {
return false
}

if (objA instanceof Map && objB instanceof Map) {
if (objA.size !== objB.size) return false

for (const [key, value] of objA) {
if (!Object.is(value, objB.get(key))) {
return false
}
if (isIterable(objA) && isIterable(objB)) {
const iteratorA = objA[Symbol.iterator]()
const iteratorB = objB[Symbol.iterator]()
let nextA = iteratorA.next()
let nextB = iteratorB.next()
if (
Array.isArray(nextA.value) &&
Array.isArray(nextB.value) &&
nextA.value.length === 2 &&
nextB.value.length === 2
) {
return compareMapLike(
objA as Iterable<[unknown, unknown]>,
objB as Iterable<[unknown, unknown]>,
)
}
return true
}

if (objA instanceof Set && objB instanceof Set) {
if (objA.size !== objB.size) return false

for (const value of objA) {
if (!objB.has(value)) {
while (!nextA.done && !nextB.done) {
if (!Object.is(nextA.value, nextB.value)) {
return false
}
nextA = iteratorA.next()
nextB = iteratorB.next()
}
return true
return !!nextA.done && !!nextB.done
}

const keysA = Object.keys(objA)
Expand Down
6 changes: 6 additions & 0 deletions tests/vanilla/shallow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ describe('shallow', () => {

expect(shallow(firstFnCompare, secondFnCompare)).toBe(false)
})

it('compares URLSearchParams', () => {
const a = new URLSearchParams({ hello: 'world' })
const b = new URLSearchParams({ zustand: 'shallow' })
expect(shallow(a, b)).toBe(false)
})
})

describe('unsupported cases', () => {
Expand Down
Loading