Skip to content

Commit

Permalink
Removing double update listenrs on externall provided motion values (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgperry committed Aug 23, 2024
1 parent 200c0bd commit 828b8d9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 15 deletions.
38 changes: 38 additions & 0 deletions dev/react/src/examples/animate-stress-external-motion-value.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { motion, useMotionValue } from "framer-motion"

function Box({ i }: { i: number }) {
const rotate = useMotionValue(0)

return (
<motion.div
initial={{ rotate: 0 }}
animate={{ rotate: 360 }}
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
style={{
rotate,
background: `hsla(${i * 10}, 100%, 50%, 1)`,
width: 100,
height: 100,
}}
/>
)
}

export const App = () => {
const boxes = Array.from(Array(1000).keys()).map((i) => (
<Box i={i} key={i} />
))

return (
<div
style={{
padding: 100,
width: "100%",
display: "flex",
flexWrap: "wrap",
}}
>
{boxes}
</div>
)
}
25 changes: 11 additions & 14 deletions packages/framer-motion/src/animation/hooks/use-animated-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react"
import { useState, useLayoutEffect } from "react"
import { useConstant } from "../../utils/use-constant"
import { TargetAndTransition } from "../../types"
import { ResolvedValues } from "../../render/types"
Expand Down Expand Up @@ -60,27 +60,24 @@ export function useAnimatedState(initialState: any) {

const element = useConstant(() => {
return new StateVisualElement(
{ props: {}, visualState, presenceContext: null },
{
props: {
onUpdate: (v) => {
setAnimationState({ ...v })
},
},
visualState,
presenceContext: null,
},
{ initialState }
)
})

useEffect(() => {
useLayoutEffect(() => {
element.mount({})
return () => element.unmount()
}, [element])

useEffect(() => {
element.update(
{
onUpdate: (v) => {
setAnimationState({ ...v })
},
},
null
)
}, [setAnimationState, element])

const startAnimation = useConstant(
() => (animationDefinition: TargetAndTransition) => {
return animateVisualElement(element, animationDefinition)
Expand Down
22 changes: 22 additions & 0 deletions packages/framer-motion/src/motion/__tests__/animate-prop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1242,4 +1242,26 @@ describe("animate prop as object", () => {

return expect(result.length).not.toBe(1)
})

test("Doesn't double-add listeners to externally-provided motion values", async () => {
const result = await new Promise<number>((resolve) => {
const Component = () => {
const x = useMotionValue(0)
return (
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.01 }}
onAnimationStart={() =>
resolve((x as any).events.change.getSize())
}
style={{ x }}
/>
)
}
const { rerender } = render(<Component />)
rerender(<Component />)
})

return expect(result).toBe(1)
})
})
6 changes: 5 additions & 1 deletion packages/framer-motion/src/render/VisualElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ export abstract class VisualElement<
}

private bindToMotionValue(key: string, value: MotionValue) {
if (this.valueSubscriptions.has(key)) {
this.valueSubscriptions.get(key)!()
}

const valueIsTransform = transformProps.has(key)

const removeOnChange = value.on(
Expand Down Expand Up @@ -508,7 +512,7 @@ export abstract class VisualElement<
FeatureConstructor &&
isEnabled(this.props)
) {
this.features[key] = new FeatureConstructor(this)
this.features[key] = new FeatureConstructor(this) as any
}

/**
Expand Down

0 comments on commit 828b8d9

Please sign in to comment.