Skip to content

Commit

Permalink
refactor: merge AnimatedProps into withAnimated
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Nov 7, 2020
1 parent 32920d6 commit 161c1ae
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 61 deletions.
4 changes: 2 additions & 2 deletions packages/animated/src/AnimatedObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class AnimatedObject extends Animated {
/** Add to a payload set. */
protected _addToPayload(this: Set<AnimatedValue>, source: any) {
const config = getFluidConfig(source)
if (config && TreeContext.current) {
TreeContext.current.dependencies.add(source)
if (config && TreeContext.dependencies) {
TreeContext.dependencies.add(source)
}
const payload = getPayload(source)
if (payload) {
Expand Down
33 changes: 0 additions & 33 deletions packages/animated/src/AnimatedProps.ts

This file was deleted.

10 changes: 6 additions & 4 deletions packages/animated/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FluidValue } from '@react-spring/shared'
import { HostConfig } from './createHost'

export type TreeContext = {
dependencies: Set<FluidValue>
host: HostConfig
/**
* Any animated values found when updating the payload of an `AnimatedObject`
* are also added to this `Set` to be observed by an animated component.
*/
dependencies: Set<FluidValue> | null
}

export const TreeContext: { current: TreeContext | null } = { current: null }
export const TreeContext: TreeContext = { dependencies: null }
1 change: 0 additions & 1 deletion packages/animated/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export * from './AnimatedValue'
export * from './AnimatedString'
export * from './AnimatedArray'
export * from './AnimatedObject'
export * from './AnimatedProps'
export * from './getAnimatedType'
export * from './createHost'
export * from './types'
79 changes: 58 additions & 21 deletions packages/animated/src/withAnimated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import { useLayoutEffect } from 'react-layout-effect'
import {
is,
each,
raf,
useForceUpdate,
FluidConfig,
useMemoOne,
useOnce,
usePrev,
FluidEvent,
FluidObserver,
FluidValue,
} from '@react-spring/shared'
import { ElementType } from '@react-spring/types'

import { AnimatedProps } from './AnimatedProps'
import { AnimatedObject } from './AnimatedObject'
import { TreeContext } from './context'
import { HostConfig } from './createHost'

export type AnimatableComponent = string | Exclude<ElementType, string>
Expand All @@ -37,49 +41,82 @@ export const withAnimated = (Component: any, host: HostConfig) => {
[givenRef]
)

const [props, deps] = getAnimatedState(givenProps, host)

const forceUpdate = useForceUpdate()
const props = new AnimatedProps(() => {
const observer = new PropsObserver(() => {
const instance = instanceRef.current
if (hasInstance && !instance) {
return // The wrapped component forgot to forward its ref.
// Either this component was unmounted before changes could be
// applied, or the wrapped component forgot to forward its ref.
return
}

const didUpdate = instance
? host.applyAnimatedValues(instance, props.getValue(true)!)
? host.applyAnimatedValues(instance, props.getValue(true))
: false

// Re-render the component when native updates fail.
if (didUpdate === false) {
forceUpdate()
}
})
}, deps)

const dependencies = new Set<FluidConfig>()
props.setValue(givenProps, { dependencies, host })
const observerRef = useRef<PropsObserver>()
useLayoutEffect(() => {
const lastObserver = observerRef.current
observerRef.current = observer

const state = [props, dependencies] as const
const stateRef = useRef(state)
const prevState = usePrev(state)
// Observe the latest dependencies.
each(deps, dep => dep.addChild(observer))

useLayoutEffect(() => {
stateRef.current = state
// Attach the new props to our latest dependencies.
each(dependencies, dep => dep.addChild(props))
// Detach the old props from our previous dependencies.
if (prevState) each(prevState[1], dep => dep.removeChild(prevState[0]))
// Stop observing previous dependencies.
if (lastObserver) {
each(lastObserver.deps, dep => dep.removeChild(lastObserver))
raf.cancel(lastObserver.update)
}
})

// Stop observing on unmount.
useOnce(() => () => {
const [props, dependencies] = stateRef.current
each(dependencies, dep => dep.removeChild(props))
const observer = observerRef.current!
each(observer.deps, dep => dep.removeChild(observer))
})

const usedProps = host.getComponentProps(props.getValue()!)
const usedProps = host.getComponentProps(props.getValue())
return <Component {...usedProps} ref={ref} />
})
}

class PropsObserver implements FluidObserver {
constructor(readonly update: () => void, readonly deps: Set<FluidValue>) {}
onParentChange(event: FluidEvent) {
if (event.type == 'change') {
raf.write(this.update)
}
}
}

type AnimatedState = [props: AnimatedObject, dependencies: Set<FluidValue>]

function getAnimatedState(props: any, host: HostConfig): AnimatedState {
const dependencies = new Set<FluidValue>()
TreeContext.dependencies = dependencies

// Search the style for dependencies.
if (props.style)
props = {
...props,
style: host.createAnimatedStyle(props.style),
}

// Search the props for dependencies.
props = new AnimatedObject(props)

TreeContext.dependencies = null
return [props, dependencies]
}

function updateRef<T>(ref: Ref<T>, value: T) {
if (ref) {
if (is.fun(ref)) ref(value)
Expand Down

0 comments on commit 161c1ae

Please sign in to comment.