Skip to content
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
2 changes: 1 addition & 1 deletion shared/chat/conversation/list-area/index.desktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {globalMargins} from '@/styles/shared'
import {FocusContext, ScrollContext} from '../normal/context'
import {chatDebugEnabled} from '@/constants/chat/debug'
import logger from '@/logger'
import shallowEqual from 'shallowequal'
import shallowEqual from '@/util/shallow-equal'
import useResizeObserver from '@/util/use-resize-observer.desktop'
import useIntersectionObserver from '@/util/use-intersection-observer'
import {useConfigState} from '@/stores/config'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react'
import type {Props} from '.'
import shallowEqual from '@/util/shallow-equal'
import {RelativeFloatingBox} from './relative-floating-box.desktop'
import noop from 'lodash/noop'
import shallowEqual from 'shallowequal'

const FloatingBox = (props: Props) => {
const {attachTo, disableEscapeKey, position, positionFallbacks, children, offset} = props
Expand Down
2 changes: 1 addition & 1 deletion shared/constants/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const useNav = () => {
}

export {wrapErrors} from '@/util/debug'
export {default as shallowEqual} from 'shallowequal'
export {default as shallowEqual} from '@/util/shallow-equal'
export {useDebouncedCallback, useThrottledCallback, type DebouncedState} from 'use-debounce'
export {useShallow, useDeep} from '@/util/zustand'
export {default as useRPC} from '@/util/use-rpc'
Expand Down
2 changes: 0 additions & 2 deletions shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@
"react-native-webview": "13.16.1",
"react-native-worklets": "0.8.1",
"react-native-zoom-toolkit": "5.0.1",
"shallowequal": "1.1.0",
"use-debounce": "10.1.1",
"util": "0.12.5",
"zustand": "5.0.12"
Expand All @@ -161,7 +160,6 @@
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/react-measure": "2.0.12",
"@types/shallowequal": "1.1.5",
"@types/webpack-env": "1.18.8",
"@typescript/native-preview": "7.0.0-dev.20260331.1",
"@testing-library/dom": "10.4.1",
Expand Down
50 changes: 50 additions & 0 deletions shared/util/shallow-equal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference types="jest" />
import shallowEqual from './shallow-equal'

test('returns true for the same reference', () => {
const value = {a: 1}

expect(shallowEqual(value, value)).toBe(true)
})

test('uses strict equality for primitives and rejects null/object mismatches', () => {
expect(shallowEqual(null, {})).toBe(false)
expect(shallowEqual({}, null)).toBe(false)
expect(shallowEqual(1, 1)).toBe(true)
expect(shallowEqual(1, 2)).toBe(false)
expect(shallowEqual(1, {})).toBe(false)
})

test('compares arrays shallowly', () => {
expect(shallowEqual([1, 2], [1, 2])).toBe(true)
expect(shallowEqual([1, {nested: true}], [1, {nested: true}])).toBe(false)
})

test('only compares own enumerable keys', () => {
const proto = {shared: 1}
const a = Object.create(proto) as {local?: number}
const b = Object.create(proto) as {local?: number}

a.local = 2
b.local = 2

expect(shallowEqual(a, b)).toBe(true)

const withOwnShared = {local: 2, shared: 1}
expect(shallowEqual(a, withOwnShared)).toBe(false)
})

test('ignores non-enumerable properties', () => {
const a = {visible: 1}
const b = {visible: 1}

Object.defineProperty(a, 'hidden', {enumerable: false, value: 1})
Object.defineProperty(b, 'hidden', {enumerable: false, value: 2})

expect(shallowEqual(a, b)).toBe(true)
})

test('uses strict equality for leaf values', () => {
expect(shallowEqual({a: Number.NaN}, {a: Number.NaN})).toBe(false)
expect(shallowEqual({a: undefined}, {a: undefined})).toBe(true)
})
38 changes: 38 additions & 0 deletions shared/util/shallow-equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const isObjectLike = (value: unknown): value is object => typeof value === 'object' && value !== null

const shallowEqual = (objA: unknown, objB: unknown): boolean => {
if (objA === objB) {
return true
}

if (!isObjectLike(objA) || !isObjectLike(objB)) {
return false
}

const recordA = objA as Record<string, unknown>
const recordB = objB as Record<string, unknown>
const keysA = Object.keys(recordA)
const keysB = Object.keys(recordB)

if (keysA.length !== keysB.length) {
return false
}

for (const key of keysA) {

if (!Object.prototype.hasOwnProperty.call(recordB, key)) {
return false
}

const valueA = recordA[key]
const valueB = recordB[key]

if (valueA !== valueB) {
return false
}
}

return true
}

export default shallowEqual
15 changes: 0 additions & 15 deletions shared/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3625,11 +3625,6 @@
"@types/http-errors" "*"
"@types/node" "*"

"@types/shallowequal@1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@types/shallowequal/-/shallowequal-1.1.5.tgz#37e4871c464981b4abee74990c73c8f414cd13dd"
integrity sha512-8afr1hbNqvZ/FBMY2mcfkkbk7xhlTZN4lVCgQf55YdjUQpWLemmrcvcHg94vjw+ZVIfPa3UZz/sOE6CkaMlDnQ==

"@types/sockjs@^0.3.36":
version "0.3.36"
resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535"
Expand Down Expand Up @@ -11351,11 +11346,6 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"

shallowequal@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==

shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
Expand Down Expand Up @@ -12164,11 +12154,6 @@ ua-parser-js@^1.0.35:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.41.tgz#bd04dc9ec830fcf9e4fad35cf22dcedd2e3b4e9c"
integrity sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==

uint8array-extras@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86"
integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==

unbox-primitive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
Expand Down