Skip to content

Commit

Permalink
Fix Animated.event attach/detach on component re-render
Browse files Browse the repository at this point in the history
Summary:
Need to make sure `detach` happens on the old `scrollableNode` before it's unmounted and that `attach` happens on the new `scrollableNode` after it's mounted. This should also be more performant because the detach step no longer requires iterating through all the props, most of which are not animated, and we filter out unneeded updates if props or ref haven't changed.

= Test Plan =

Hook up native onscroll events in `FlatListExample` and toggle "debug" - before, the events would stop working because they would try to attach to the old unmounted node, but with this diff it keeps working as expected.

Reviewed By: fkgozali

Differential Revision: D4687186

fbshipit-source-id: 313a7964d4614190308490a51fc4f56abb6690f8
  • Loading branch information
sahrens authored and facebook-github-bot committed Mar 18, 2017
1 parent f48b54b commit 9c3e6ae
Showing 1 changed file with 35 additions and 39 deletions.
74 changes: 35 additions & 39 deletions Libraries/Animated/src/AnimatedImplementation.js
Expand Up @@ -1510,9 +1510,9 @@ class AnimatedStyle extends AnimatedWithChildren {


// Recursively get values for nested styles (like iOS's shadowOffset) // Recursively get values for nested styles (like iOS's shadowOffset)
__walkStyleAndGetValues(style) { __walkStyleAndGetValues(style) {
let updatedStyle = {}; const updatedStyle = {};
for (let key in style) { for (const key in style) {
let value = style[key]; const value = style[key];
if (value instanceof Animated) { if (value instanceof Animated) {
if (!value.__isNative) { if (!value.__isNative) {
// We cannot use value of natively driven nodes this way as the value we have access from // We cannot use value of natively driven nodes this way as the value we have access from
Expand All @@ -1535,9 +1535,9 @@ class AnimatedStyle extends AnimatedWithChildren {


// Recursively get animated values for nested styles (like iOS's shadowOffset) // Recursively get animated values for nested styles (like iOS's shadowOffset)
__walkStyleAndGetAnimatedValues(style) { __walkStyleAndGetAnimatedValues(style) {
let updatedStyle = {}; const updatedStyle = {};
for (let key in style) { for (const key in style) {
let value = style[key]; const value = style[key];
if (value instanceof Animated) { if (value instanceof Animated) {
updatedStyle[key] = value.__getAnimatedValue(); updatedStyle[key] = value.__getAnimatedValue();
} else if (value && !Array.isArray(value) && typeof value === 'object') { } else if (value && !Array.isArray(value) && typeof value === 'object') {
Expand Down Expand Up @@ -1690,7 +1690,9 @@ class AnimatedProps extends Animated {
} }


setNativeView(animatedView: any): void { setNativeView(animatedView: any): void {
invariant(this._animatedView === undefined, 'Animated view already set.'); if (this._animatedView === animatedView) {
return;
}
this._animatedView = animatedView; this._animatedView = animatedView;
if (this.__isNative) { if (this.__isNative) {
this.__connectAnimatedView(); this.__connectAnimatedView();
Expand Down Expand Up @@ -1729,7 +1731,9 @@ class AnimatedProps extends Animated {
function createAnimatedComponent(Component: any): any { function createAnimatedComponent(Component: any): any {
class AnimatedComponent extends React.Component { class AnimatedComponent extends React.Component {
_component: any; _component: any;
_prevComponent: any;
_propsAnimated: AnimatedProps; _propsAnimated: AnimatedProps;
_eventDetachers: Array<Function> = [];
_setComponentRef: Function; _setComponentRef: Function;


constructor(props: Object) { constructor(props: Object) {
Expand All @@ -1739,7 +1743,7 @@ function createAnimatedComponent(Component: any): any {


componentWillUnmount() { componentWillUnmount() {
this._propsAnimated && this._propsAnimated.__detach(); this._propsAnimated && this._propsAnimated.__detach();
this._detachNativeEvents(this.props); this._detachNativeEvents();
} }


setNativeProps(props) { setNativeProps(props) {
Expand All @@ -1752,42 +1756,28 @@ function createAnimatedComponent(Component: any): any {


componentDidMount() { componentDidMount() {
this._propsAnimated.setNativeView(this._component); this._propsAnimated.setNativeView(this._component);

this._attachNativeEvents();
this._attachNativeEvents(this.props);
} }


_attachNativeEvents(newProps) { _attachNativeEvents() {
if (newProps !== this.props) {
this._detachNativeEvents(this.props);
}

// Make sure to get the scrollable node for components that implement // Make sure to get the scrollable node for components that implement
// `ScrollResponder.Mixin`. // `ScrollResponder.Mixin`.
const ref = this._component.getScrollableNode ? const scrollableNode = this._component.getScrollableNode ?
this._component.getScrollableNode() : this._component.getScrollableNode() :
this._component; this._component;


for (const key in newProps) { for (const key in this.props) {
const prop = newProps[key]; const prop = this.props[key];
if (prop instanceof AnimatedEvent && prop.__isNative) { if (prop instanceof AnimatedEvent && prop.__isNative) {
prop.__attach(ref, key); prop.__attach(scrollableNode, key);
this._eventDetachers.push(() => prop.__detach(scrollableNode, key));
} }
} }
} }


_detachNativeEvents(props) { _detachNativeEvents() {
// Make sure to get the scrollable node for components that implement this._eventDetachers.forEach(remove => remove());
// `ScrollResponder.Mixin`. this._eventDetachers = [];
const ref = this._component.getScrollableNode ?
this._component.getScrollableNode() :
this._component;

for (const key in props) {
const prop = props[key];
if (prop instanceof AnimatedEvent && prop.__isNative) {
prop.__detach(ref, key);
}
}
} }


_attachProps(nextProps) { _attachProps(nextProps) {
Expand Down Expand Up @@ -1820,10 +1810,6 @@ function createAnimatedComponent(Component: any): any {
callback, callback,
); );


if (this._component) {
this._propsAnimated.setNativeView(this._component);
}

// When you call detach, it removes the element from the parent list // When you call detach, it removes the element from the parent list
// of children. If it goes to 0, then the parent also detaches itself // of children. If it goes to 0, then the parent also detaches itself
// and so on. // and so on.
Expand All @@ -1835,9 +1821,18 @@ function createAnimatedComponent(Component: any): any {
oldPropsAnimated && oldPropsAnimated.__detach(); oldPropsAnimated && oldPropsAnimated.__detach();
} }


componentWillReceiveProps(nextProps) { componentWillReceiveProps(newProps) {
this._attachProps(nextProps); this._attachProps(newProps);
this._attachNativeEvents(nextProps); }

componentDidUpdate(prevProps) {
if (this._component !== this._prevComponent) {
this._propsAnimated.setNativeView(this._component);
}
if (this._component !== this._prevComponent || prevProps !== this.props) {
this._detachNativeEvents();
this._attachNativeEvents();
}
} }


render() { render() {
Expand All @@ -1850,6 +1845,7 @@ function createAnimatedComponent(Component: any): any {
} }


_setComponentRef(c) { _setComponentRef(c) {
this._prevComponent = this._component;
this._component = c; this._component = c;
} }


Expand Down

2 comments on commit 9c3e6ae

@oblador
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@janicduplessis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix!

Please sign in to comment.