AlexWayne
AlexWayne6mo ago

v10 useQueries does not return a stable reference

I though this code would work downstream for memos and callbacks, but it does not. The memo and effect are re-ran every render. Have I don't something stupid, here or is this intended? How would I memoize a derives values from the results? This code:
const results = trpcReactQuery.useQueries((trpc) =>
things.map((thing) =>
trpc.things.byId({ thingId: thing.id }),
),
)
console.log('')
console.log('--- render ---')

useEffect(() => {
console.log('results changed', results.length)
}, [results])

const foo = useMemo(() => {
console.log('memo recreated')
return results.map((result) => result.data?.id)
}, [results])
const results = trpcReactQuery.useQueries((trpc) =>
things.map((thing) =>
trpc.things.byId({ thingId: thing.id }),
),
)
console.log('')
console.log('--- render ---')

useEffect(() => {
console.log('results changed', results.length)
}, [results])

const foo = useMemo(() => {
console.log('memo recreated')
return results.map((result) => result.data?.id)
}, [results])
Logs this:
--- render ---
memo recreated
results changed 0

--- render ---
memo recreated
results changed 0

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28
--- render ---
memo recreated
results changed 0

--- render ---
memo recreated
results changed 0

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28

--- render ---
memo recreated
results changed 28
1 Reply
AlexWayne
AlexWayneOP6mo ago
This does work, which means the data prop is stable
const foo = useMemo(() => {
console.log('memo recreated')
return results.map((result) => result.data?.id)
}, [results[0]?.data])
const foo = useMemo(() => {
console.log('memo recreated')
return results.map((result) => result.data?.id)
}, [results[0]?.data])
but I'm not seeing how to get a stable array of the data objects themselves I guess trpc v11 and tanstack query v5 fix this with the combine option. Maybe I should focus on being able to upgrade instead Ended up with
function useMemoizedResultData<T>(results: { data: T }[]): T[] {
const previousResultData = useRef<T[]>([])
const resultData = results.map((result) => result.data)

if (areArraysEqual(previousResultData.current, resultData)) {
return previousResultData.current
}

previousResultData.current = resultData
return resultData
}

/** Returns true when `a` and `b` are arrays with perfectly identical contents. */
function areArraysEqual(a?: unknown[], b?: unknown[]): boolean {
if (a === b) return true
if (a?.length !== b?.length) return false
return !!a?.every((item, i) => item === b?.[i])
}
function useMemoizedResultData<T>(results: { data: T }[]): T[] {
const previousResultData = useRef<T[]>([])
const resultData = results.map((result) => result.data)

if (areArraysEqual(previousResultData.current, resultData)) {
return previousResultData.current
}

previousResultData.current = resultData
return resultData
}

/** Returns true when `a` and `b` are arrays with perfectly identical contents. */
function areArraysEqual(a?: unknown[], b?: unknown[]): boolean {
if (a === b) return true
if (a?.length !== b?.length) return false
return !!a?.every((item, i) => item === b?.[i])
}
Which feels like a bit of a hack, but it seems to work.