Skip to content

Commit

Permalink
[Web] Fix hiding component with entering on every render (#5278)
Browse files Browse the repository at this point in the history
Requires #5282.

## Summary

Some TS shenanigans as a necessary bonus.

<table>
<tr><td>BEFORE</td><td>AFTER</td></tr>
<tr><td>


https://github.com/software-mansion/react-native-reanimated/assets/40713406/d13c9186-f1e5-4e12-bbb1-704b1eac0c08

</td><td>


https://github.com/software-mansion/react-native-reanimated/assets/40713406/804fbcfb-f0f0-4b3f-9af3-c46617ffed18

</td></tr>
</table>




## Test plan

<details>

```tsx
import { StyleSheet, View, Button } from 'react-native';

import Animated, { FadeIn } from 'react-native-reanimated';

import React from 'react';

export default function EmptyExample() {
  const [shouldBeUseEntering, setShouldBeUseEntering] = React.useState(false);

  function handlePress() {
    setShouldBeUseEntering(!shouldBeUseEntering);
  }

  return (
    <View style={styles.container}>
      <Button title="ShouldBeUseEntering" onPress={handlePress} />
      <Animated.View
        style={styles.box}
        entering={shouldBeUseEntering ? FadeIn : undefined}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: 'blue',
  },
});
```

</details>

---------

Co-authored-by: Michał Bert <michal.bert@swmansion.com>
  • Loading branch information
2 people authored and Aleksandra Cynk committed Dec 12, 2023
1 parent b5ca98d commit 35ae2ff
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 52 deletions.
13 changes: 3 additions & 10 deletions src/createAnimatedComponent/PropsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ function dummyListener() {

export class PropsFilter implements IPropsFilter {
private _initialStyle = {};
private _isFirstRender = true;

public filterNonAnimatedProps(
component: React.Component<unknown, unknown> & IAnimatedComponentInternal
Expand All @@ -39,7 +38,7 @@ export class PropsFilter implements IPropsFilter {
if (style && style.viewDescriptors) {
// this is how we recognize styles returned by useAnimatedStyle
style.viewsRef.add(component);
if (this._isFirstRender) {
if (component._isFirstRender) {
this._initialStyle = {
...style.initial.value,
...this._initialStyle,
Expand All @@ -48,7 +47,7 @@ export class PropsFilter implements IPropsFilter {
}
return this._initialStyle;
} else if (hasInlineStyles(style)) {
return getInlineStyle(style, this._isFirstRender);
return getInlineStyle(style, component._isFirstRender);
} else {
return style;
}
Expand Down Expand Up @@ -78,7 +77,7 @@ export class PropsFilter implements IPropsFilter {
props[key] = dummyListener;
}
} else if (isSharedValue(value)) {
if (this._isFirstRender) {
if (component._isFirstRender) {
props[key] = (value as SharedValue<unknown>).value;
}
} else if (key !== 'onGestureHandlerStateChange' || !isChromeDebugger()) {
Expand All @@ -87,10 +86,4 @@ export class PropsFilter implements IPropsFilter {
}
return props;
}

public onRender() {
if (this._isFirstRender) {
this._isFirstRender = false;
}
}
}
8 changes: 5 additions & 3 deletions src/createAnimatedComponent/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,12 @@ export function createAnimatedComponent(
startWebLayoutAnimation(
this.props,
this._component as HTMLElement,
LayoutAnimationType.ENTERING
LayoutAnimationType.ENTERING,
!!(this._isFirstRender && this.props.entering)
);
}

this._isFirstRender = false;
}

componentWillUnmount() {
Expand Down Expand Up @@ -507,7 +510,6 @@ export function createAnimatedComponent(

render() {
const props = this._PropsFilter.filterNonAnimatedProps(this);
this._PropsFilter.onRender();

if (isJest()) {
props.animatedStyle = this.animatedStyle;
Expand All @@ -517,7 +519,7 @@ export function createAnimatedComponent(
// Because of that we can encounter a situation in which component is visible for a short amount of time, and later on animation triggers.
// I've tested that on various browsers and devices and it did not happen to me. To be sure that it won't happen to someone else,
// I've decided to hide component at first render. Its visibility is reset in `componentDidMount`.
if (IS_WEB && props.entering) {
if (this._isFirstRender && IS_WEB && props.entering) {
props.style = {
...(props.style ?? {}),
visibility: 'hidden', // Hide component until `componentDidMount` triggers
Expand Down
79 changes: 45 additions & 34 deletions src/reanimated2/layoutReanimation/web/animationsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,26 @@ function chooseConfig<ComponentProps extends Record<string, unknown>>(

function checkUndefinedAnimationFail(
initialAnimationName: string,
isLayoutTransition: boolean,
hasEnteringAnimation: boolean,
element: HTMLElement
isLayoutTransition: boolean
) {
// This prevents crashes if we try to set animations that are not defined.
// We don't care about layout transitions since they're created dynamically
if (initialAnimationName in Animations || isLayoutTransition) {
return false;
}

if (hasEnteringAnimation) {
makeElementVisible(element);
}

console.warn(
"[Reanimated] Couldn't load entering/exiting animation. Current version supports only predefined animations with modifiers: duration, delay, easing, randomizeDelay, wtihCallback, reducedMotion."
);

return true;
}

function checkReduceMotionFail(
animationConfig: AnimationConfig,
hasEnteringAnimation: boolean,
element: HTMLElement
) {
function checkReduceMotionFail(animationConfig: AnimationConfig) {
if (!animationConfig.reduceMotion) {
return false;
}

if (hasEnteringAnimation) {
makeElementVisible(element);
}

return true;
}

Expand Down Expand Up @@ -100,33 +86,28 @@ function chooseAction(
}
}

export function startWebLayoutAnimation<
function tryGetAnimationConfigWithTransform<
ComponentProps extends Record<string, unknown>
>(
props: Readonly<AnimatedComponentProps<ComponentProps>>,
element: HTMLElement,
animationType: LayoutAnimationType,
transitionData?: TransitionData
animationType: LayoutAnimationType
) {
const config = chooseConfig(animationType, props);
if (!config) {
return;
return null;
}

const hasEnteringAnimation = props.entering !== undefined;
const isLayoutTransition = animationType === LayoutAnimationType.LAYOUT;
const initialAnimationName =
typeof config === 'function' ? config.name : config.constructor.name;

const shouldFail = checkUndefinedAnimationFail(
initialAnimationName,
isLayoutTransition,
hasEnteringAnimation,
element
isLayoutTransition
);

if (shouldFail) {
return;
return null;
}

const transform = extractTransformFromStyle(props.style as StyleProps);
Expand All @@ -142,17 +123,46 @@ export function startWebLayoutAnimation<
initialAnimationName as AnimationNames
);

if (checkReduceMotionFail(animationConfig, hasEnteringAnimation, element)) {
return;
if (checkReduceMotionFail(animationConfig)) {
return null;
}

chooseAction(
animationType,
animationConfig,
element,
transitionData as TransitionData,
transform
return { animationConfig, transform };
}

export function startWebLayoutAnimation<
ComponentProps extends Record<string, unknown>
>(
props: Readonly<AnimatedComponentProps<ComponentProps>>,
element: HTMLElement,
animationType: LayoutAnimationType,
shouldMakeVisible = false,
transitionData?: TransitionData
) {
let visibilityDelay = 0;

const maybeAnimationConfigWithTransform = tryGetAnimationConfigWithTransform(
props,
animationType
);

if (maybeAnimationConfigWithTransform) {
const { animationConfig, transform } = maybeAnimationConfigWithTransform;

visibilityDelay = animationConfig.delay;

chooseAction(
animationType,
animationConfig,
element,
transitionData as TransitionData,
transform
);
}

if (shouldMakeVisible) {
makeElementVisible(element, visibilityDelay);
}
}

export function tryActivateLayoutTransition<
Expand Down Expand Up @@ -184,6 +194,7 @@ export function tryActivateLayoutTransition<
props,
element,
LayoutAnimationType.LAYOUT,
false,
transitionData
);
}
19 changes: 14 additions & 5 deletions src/reanimated2/layoutReanimation/web/componentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,20 @@ export function getProcessedConfig(
};
}

export function makeElementVisible(element: HTMLElement) {
_updatePropsJS(
{ visibility: 'initial' },
{ _component: element as ReanimatedHTMLElement }
);
export function makeElementVisible(element: HTMLElement, delay: number) {
if (delay === 0) {
_updatePropsJS(
{ visibility: 'initial' },
{ _component: element as ReanimatedHTMLElement }
);
} else {
setTimeout(() => {
_updatePropsJS(
{ visibility: 'initial' },
{ _component: element as ReanimatedHTMLElement }
);
}, delay * 1000);
}
}

export function setElementAnimation(
Expand Down

0 comments on commit 35ae2ff

Please sign in to comment.