Skip to content

Commit

Permalink
fix: useSprings with props function
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aleclarson committed Jan 28, 2020
1 parent 8059861 commit 55c5691
Showing 1 changed file with 33 additions and 20 deletions.
53 changes: 33 additions & 20 deletions packages/core/src/useSprings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, RefObject, useImperativeHandle } from 'react'
import { useMemo, RefObject, useImperativeHandle, useState } from 'react'
import { useLayoutEffect } from 'react-layout-effect'
import { is, each, usePrev, useOnce, UnknownProps, Merge } from 'shared'

Expand Down Expand Up @@ -70,23 +70,36 @@ export function useSprings(
deps?: any[]
): any {
const propsFn = is.fun(props) && props
if (deps) deps = deps.concat(length)
if (propsFn && !deps) deps = []

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

const ctrls: Controller[] = useMemoOne(() => [], [])
const [ctrls] = useState<Controller[]>([])
const updates: ControllerProps[] = []
const prevLength = usePrev(length) || 0

// Create new controllers when "length" increases, and destroy
// the affected controllers when "length" decreases.
useMemoOne(() => {
// Note: Length changes are unsafe in React concurrent mode.
if (prevLength > length) {
for (let i = length; i < prevLength; i++) {
ctrls[i].dispose()
}
}
ctrls.length = length
for (let i = 0; i < length; i++) {
getUpdates(prevLength, length)
}, [length])

// Update existing controllers when "deps" are changed.
useMemoOne(() => {
getUpdates(0, prevLength)
}, deps)

function getUpdates(startIndex: number, endIndex: number) {
for (let i = startIndex; i < endIndex; i++) {
const ctrl = ctrls[i] || (ctrls[i] = new Controller())
const update: UseSpringProps<any> = propsFn
? propsFn(i, ctrl)
Expand All @@ -106,7 +119,19 @@ export function useSprings(
}
}
}
}, deps)
}

// Controllers are not updated until the commit phase.
useLayoutEffect(() => {
each(updates, (update, i) => ctrls[i].update(update))
if (!ref) {
each(ctrls, ctrl => ctrl.start())
}
})

useOnce(() => () => {
each(ctrls, ctrl => ctrl.dispose())
})

const api = useMemo(
(): SpringHandle => ({
Expand Down Expand Up @@ -136,20 +161,8 @@ export function useSprings(

useImperativeHandle(ref, () => api)

const isRenderDriven = !propsFn && arguments.length < 3
if (isRenderDriven) deps = undefined

useLayoutEffect(() => {
each(updates, (update, i) => ctrls[i].update(update))
if (!ref) {
each(ctrls, ctrl => ctrl.start())
}
}, deps)

useOnce(() => () => {
each(ctrls, ctrl => ctrl.dispose())
})

const values = ctrls.map(ctrl => ({ ...ctrl.springs }))
return isRenderDriven ? values : [values, api.update, api.stop]
return propsFn || arguments.length == 3
? [values, api.update, api.stop]
: values
}

0 comments on commit 55c5691

Please sign in to comment.