Skip to content

Commit

Permalink
🔬 Make useRawLogs more efficient and responsive (#1151)
Browse files Browse the repository at this point in the history
* Make useRawLogs more efficient and responsive
- Makes use of isStatic query param
- Makes use of refresh query param and global config
- Clears logs when there's a change in the filter to minimize the amount of time invalid old logs are returned
- Prevents some race conditions
- It no longer fetches logs with every re-render
- Filter can no longer be a promise - reduces unnecessary complexity

* Improve useRawLogs
- Switch back to allowing filter promises
- Switch back to useEffect to update the logs
- Shallow copy filter data before the async getLogs to prevent data mismatch making logs stale
- Prevent changing state after component unmount
- Use useRef to track loading status

* Fix and simplify nullish checking in deepEqual

* Remove comment in deepEqual

* Cleanup useResolvedFilter

* Move isPrimitive and deepEqual to common helper

* Fix bug in deepEqual

* Add comment to deepEqual

* Make slight deepEqual performance improvement

* Add tests for deepEqual and isPrimitive

* Export common in helpers

* Extract useResolvedFilter to useResolvedPromise

* Add tests for useResolvedPromise

* Remove unused import

* Fix code smell

* Disable unused var linting

* Improve performance of useResolvedPromise

* Fix staleness issue

* Fix bugs in useResolvedPromise

* Disable buggy optimization

* Create hot-pots-tap.md

---------

Co-authored-by: Justyna Broniszewska <[email protected]>
Co-authored-by: Łukasz Stankiewicz <[email protected]>
  • Loading branch information
3 people authored May 27, 2024
1 parent 5a626b8 commit fb44f98
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-pots-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@usedapp/core": patch
---

🔬 Make useRawLogs more efficient and responsive
269 changes: 269 additions & 0 deletions packages/core/src/helpers/common.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import { expect } from 'chai'
import { deepEqual, isPrimitive } from './common'
import { Filter } from '@ethersproject/abstract-provider'

describe('common', function () {
describe('isPrimitive', function () {
it('Returns true for 0', function () {
expect(isPrimitive(0)).to.equal(true)
})

it('Returns true for 1', function () {
expect(isPrimitive(1)).to.equal(true)
})

it("Returns true for 'a'", function () {
expect(isPrimitive('a')).to.equal(true)
})

it("Returns true for ''", function () {
expect(isPrimitive('')).to.equal(true)
})

it('Returns true for undefined', function () {
expect(isPrimitive(undefined)).to.equal(true)
})

it('Returns true for null', function () {
expect(isPrimitive(null)).to.equal(true)
})

it('Returns true for true', function () {
expect(isPrimitive(true)).to.equal(true)
})

it('Returns true for false', function () {
expect(isPrimitive(false)).to.equal(true)
})

it('Returns false for []', function () {
expect(isPrimitive([])).to.equal(false)
})

it('Returns false for {}', function () {
expect(isPrimitive({})).to.equal(false)
})
})

describe('deepEqual', function () {
it('Returns true for 0 and 0', function () {
expect(deepEqual(0, 0)).to.equal(true)
})

it('Returns true for 1 and 1', function () {
expect(deepEqual(1, 1)).to.equal(true)
})

it("Returns true for 'a' and 'a'", function () {
expect(deepEqual('a', 'a')).to.equal(true)
})

it("Returns true for '' and ''", function () {
expect(deepEqual('', '')).to.equal(true)
})

it('Returns true for undefined and undefined', function () {
expect(deepEqual(undefined, undefined)).to.equal(true)
})

it('Returns true for null and null', function () {
expect(deepEqual(null, null)).to.equal(true)
})

it('Returns true for true and true', function () {
expect(deepEqual(true, true)).to.equal(true)
})

it('Returns true for false and false', function () {
expect(deepEqual(false, false)).to.equal(true)
})

it('Returns true for [] and []', function () {
expect(deepEqual([], [])).to.equal(true)
})

it('Returns true for undefined and null', function () {
expect(deepEqual(undefined, null)).to.equal(true)
})

it('Returns true for null and undefined', function () {
expect(deepEqual(null, undefined)).to.equal(true)
})

it('Returns true for two block filters with different property orderings', function () {
const filter1: Filter = {
fromBlock: 1,
toBlock: 2,
}

const filter2: Filter = {
toBlock: 2,
fromBlock: 1,
}

expect(deepEqual(filter1, filter2)).to.equal(true)
})

it('Returns false for two block filters with different property counts (one has less than two)', function () {
const filter1: Filter = {
fromBlock: 1,
}

const filter2: Filter = {
fromBlock: 1,
toBlock: 2,
}

expect(deepEqual(filter1, filter2)).to.equal(false)
})

it('Returns false for two block filters with different property counts (one has more than two)', function () {
const filter1: Filter = {
fromBlock: 1,
toBlock: 2,
}

const filter2: Filter = {
fromBlock: 1,
}

expect(deepEqual(filter1, filter2)).to.equal(false)
})

it('Returns false for two block filters with different addresses', function () {
const filter1: Filter = {
address: '0x0000000000000000000000000000000000000000',
}

const filter2: Filter = {
address: '0x0000000000000000000000000000000000000001',
}

expect(deepEqual(filter1, filter2)).to.equal(false)
})

it("Returns false for 0 and '0'", function () {
expect(deepEqual(0, '0')).to.equal(false)
})

it("Returns false for '0' and 0", function () {
expect(deepEqual('0', 0)).to.equal(false)
})

it('Returns false for false and 0', function () {
expect(deepEqual(false, 0)).to.equal(false)
})

it('Returns false for 0 and false', function () {
expect(deepEqual(0, false)).to.equal(false)
})

it('Returns false for 0 and null', function () {
expect(deepEqual(0, null)).to.equal(false)
})

it('Returns false for null and 0', function () {
expect(deepEqual(null, 0)).to.equal(false)
})

it('Returns false for 0 and undefined', function () {
expect(deepEqual(0, undefined)).to.equal(false)
})

it('Returns false for undefined and 0', function () {
expect(deepEqual(undefined, 0)).to.equal(false)
})

it("Returns false for '' and null", function () {
expect(deepEqual('', null)).to.equal(false)
})

it("Returns false for null and ''", function () {
expect(deepEqual(null, '')).to.equal(false)
})

it("Returns false for '' and undefined", function () {
expect(deepEqual('', undefined)).to.equal(false)
})

it("Returns false for undefined and ''", function () {
expect(deepEqual(undefined, '')).to.equal(false)
})

it('Returns false for {} and {}', function () {
expect(deepEqual({}, {})).to.equal(true)
})

it('Returns false for 0 and 1', function () {
expect(deepEqual(0, 1)).to.equal(false)
})

it('Returns false for 1 and 0', function () {
expect(deepEqual(1, 0)).to.equal(false)
})

it("Returns false for 'a' and 'b'", function () {
expect(deepEqual('a', 'b')).to.equal(false)
})

it("Returns false for 'b' and 'a'", function () {
expect(deepEqual('b', 'a')).to.equal(false)
})

it("Returns false for '' and 'a'", function () {
expect(deepEqual('', 'a')).to.equal(false)
})

it("Returns false for 'a' and ''", function () {
expect(deepEqual('a', '')).to.equal(false)
})

it('Retuens false for [0] and [0, 0]', function () {
expect(deepEqual([0], [0, 0])).to.equal(false)
})

it('Returns false for [0, 0] and [0]', function () {
expect(deepEqual([0, 0], [0])).to.equal(false)
})

it('Returns false for [0] and [1]', function () {
expect(deepEqual([0], [1])).to.equal(false)
})

it('Returns false for [1] and [0]', function () {
expect(deepEqual([1], [0])).to.equal(false)
})

it('Returns false for [0] and [null]', function () {
expect(deepEqual([0], [null])).to.equal(false)
})

it('Returns false for [null] and [0]', function () {
expect(deepEqual([null], [0])).to.equal(false)
})

it('Returns false for [0] and [undefined]', function () {
expect(deepEqual([0], [undefined])).to.equal(false)
})

it('Returns false for [undefined] and [0]', function () {
expect(deepEqual([undefined], [0])).to.equal(false)
})

it('Returns false for [0] and [{}]', function () {
expect(deepEqual([0], [{}])).to.equal(false)
})

it('Returns false for [{}] and [0]', function () {
expect(deepEqual([{}], [0])).to.equal(false)
})

it('Returns false for [0, 1] and [1, 0]', function () {
expect(deepEqual([0, 1], [1, 0])).to.equal(false)
})

it('Returns false for [1, 0] and [0, 1]', function () {
expect(deepEqual([1, 0], [0, 1])).to.equal(false)
})
})
})
44 changes: 44 additions & 0 deletions packages/core/src/helpers/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
export function shortenString(str: string) {
return str.substring(0, 6) + '...' + str.substring(str.length - 4)
}

/**
* Determines whether two objects are equal using a deep comparison. Null and undefined are considered equal. Arrays
* with the same elements are not considered equal if they are in different orders. Objects with the same properties
* can have different property orderings and still be considered equal.
* @param obj1 The first object to compare.
* @param obj2 The second object to compare.
* @returns True if the objects are deep equal, false otherwise.
*/
export function deepEqual(obj1: any, obj2: any) {
if (obj1 === obj2) return true

if (obj1 == null || obj2 == null) return obj1 == obj2

const obj1Primitive = isPrimitive(obj1)
const obj2Primitive = isPrimitive(obj2)
if (obj1Primitive || obj2Primitive)
// compare primitives
return obj1Primitive === obj2Primitive && obj1 === obj2

let obj1KeyCount = 0
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ in obj1) obj1KeyCount++

let obj2KeyCount = 0
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ in obj2) {
if (++obj2KeyCount > obj1KeyCount) return false
}

if (obj1KeyCount !== obj2KeyCount) return false

// compare objects with same number of keys
for (const key in obj1) {
if (!(key in obj2)) return false //other object doesn't have this prop
if (!deepEqual(obj1[key], obj2[key])) return false
}

return true
}

export function isPrimitive(obj: any) {
return obj !== Object(obj)
}
1 change: 1 addition & 0 deletions packages/core/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './eip1193'
export * from './logs'
export * from './isWebSocketProvider'
export * from './event'
export * from './common'
1 change: 1 addition & 0 deletions packages/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export * from './useRawLogs'
export * from './useRawCalls'
export * from './useResolveName'
export * from './useSigner'
export * from './useResolvedPromise'
Loading

3 comments on commit fb44f98

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.