Skip to content

Commit 55c5691

Browse files
committed
fix: useSprings with props function
This regression was introduced in the previous canary version. It caused the props function to be called on every render. The deps no longer have the "length" argument included by default, because we handle length changes in a separate useMemo call now. If you want to update all springs when the length changes, you must include the length in your deps array explicitly.
1 parent 8059861 commit 55c5691

File tree

1 file changed

+33
-20
lines changed

1 file changed

+33
-20
lines changed

packages/core/src/useSprings.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, RefObject, useImperativeHandle } from 'react'
1+
import { useMemo, RefObject, useImperativeHandle, useState } from 'react'
22
import { useLayoutEffect } from 'react-layout-effect'
33
import { is, each, usePrev, useOnce, UnknownProps, Merge } from 'shared'
44

@@ -70,23 +70,36 @@ export function useSprings(
7070
deps?: any[]
7171
): any {
7272
const propsFn = is.fun(props) && props
73-
if (deps) deps = deps.concat(length)
73+
if (propsFn && !deps) deps = []
7474

7575
// The "ref" prop is taken from the props of the first spring only.
7676
// The ref is assumed to *never* change after the first render.
7777
let ref: RefObject<SpringHandle> | undefined
7878

79-
const ctrls: Controller[] = useMemoOne(() => [], [])
79+
const [ctrls] = useState<Controller[]>([])
8080
const updates: ControllerProps[] = []
8181
const prevLength = usePrev(length) || 0
82+
83+
// Create new controllers when "length" increases, and destroy
84+
// the affected controllers when "length" decreases.
8285
useMemoOne(() => {
86+
// Note: Length changes are unsafe in React concurrent mode.
8387
if (prevLength > length) {
8488
for (let i = length; i < prevLength; i++) {
8589
ctrls[i].dispose()
8690
}
8791
}
8892
ctrls.length = length
89-
for (let i = 0; i < length; i++) {
93+
getUpdates(prevLength, length)
94+
}, [length])
95+
96+
// Update existing controllers when "deps" are changed.
97+
useMemoOne(() => {
98+
getUpdates(0, prevLength)
99+
}, deps)
100+
101+
function getUpdates(startIndex: number, endIndex: number) {
102+
for (let i = startIndex; i < endIndex; i++) {
90103
const ctrl = ctrls[i] || (ctrls[i] = new Controller())
91104
const update: UseSpringProps<any> = propsFn
92105
? propsFn(i, ctrl)
@@ -106,7 +119,19 @@ export function useSprings(
106119
}
107120
}
108121
}
109-
}, deps)
122+
}
123+
124+
// Controllers are not updated until the commit phase.
125+
useLayoutEffect(() => {
126+
each(updates, (update, i) => ctrls[i].update(update))
127+
if (!ref) {
128+
each(ctrls, ctrl => ctrl.start())
129+
}
130+
})
131+
132+
useOnce(() => () => {
133+
each(ctrls, ctrl => ctrl.dispose())
134+
})
110135

111136
const api = useMemo(
112137
(): SpringHandle => ({
@@ -136,20 +161,8 @@ export function useSprings(
136161

137162
useImperativeHandle(ref, () => api)
138163

139-
const isRenderDriven = !propsFn && arguments.length < 3
140-
if (isRenderDriven) deps = undefined
141-
142-
useLayoutEffect(() => {
143-
each(updates, (update, i) => ctrls[i].update(update))
144-
if (!ref) {
145-
each(ctrls, ctrl => ctrl.start())
146-
}
147-
}, deps)
148-
149-
useOnce(() => () => {
150-
each(ctrls, ctrl => ctrl.dispose())
151-
})
152-
153164
const values = ctrls.map(ctrl => ({ ...ctrl.springs }))
154-
return isRenderDriven ? values : [values, api.update, api.stop]
165+
return propsFn || arguments.length == 3
166+
? [values, api.update, api.stop]
167+
: values
155168
}

0 commit comments

Comments
 (0)