Skip to content

Initial style for component with shared style#2431

Merged
piaskowyk merged 2 commits intomasterfrom
@piaskowyk/initial-shared-style
Sep 24, 2021
Merged

Initial style for component with shared style#2431
piaskowyk merged 2 commits intomasterfrom
@piaskowyk/initial-shared-style

Conversation

@piaskowyk
Copy link
Member

@piaskowyk piaskowyk commented Sep 16, 2021

Description

In the previous approach, initialUpdaterRun was called inside createAnimatedComponent(), and if createAnimatedComponent was called during an animation, it can cause an inconsistent state of style because on UI thread was scheduled to update and on JS thread was computed current style. But on the UI thread, this update can be executed before action on the JS thread or after them.
I moved initialUpdaterRun to createAnimatedComponent and compute initial style in the component's constructor.

Fixes: #2406

@piaskowyk piaskowyk self-assigned this Sep 20, 2021
@piaskowyk piaskowyk merged commit ad9b5d6 into master Sep 24, 2021
@piaskowyk piaskowyk deleted the @piaskowyk/initial-shared-style branch September 24, 2021 08:02
piaskowyk added a commit that referenced this pull request Mar 18, 2022
## Description

We can't use just initial style as the default style in every render because these styles can be outdated. We can't change the default style after the first render, because after the second render we don't run mapper that's why the component can change the style to the initial value.

Related:
- #2580
- #2431
- #2406
- #1470

### code

Before

https://user-images.githubusercontent.com/36106620/158142874-a11191e7-c0d9-4c3f-8f18-e5b540a6f17c.mov

https://user-images.githubusercontent.com/36106620/158167177-81dfa334-db01-4e04-a234-e1069e8d715b.mov




After

https://user-images.githubusercontent.com/36106620/149799832-b0c0748d-2d9d-42b9-b9ba-f6492cc1fbf0.mov

<details>
<summary>code</summary>

```js
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  Easing,
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React, { useState } from 'react';

export default function AnimatedStyleUpdateExample(props:any) {
  const randomWidth = useSharedValue(10);

  const [counter, setCounter] = useState(0);
  const [counter2, setCounter2] = useState(0);
  const [itemList, setItemList] = useState([]);
  const [toggleState, setToggleState] = useState(false);

  const config = {
    duration: 500,
    easing: Easing.bezier(0.5, 0.01, 0, 1),
  };

  const style = useAnimatedStyle(() => {
    return {
      width: withTiming(randomWidth.value, config),
    };
  });

  const staticObject = <Animated.View
    style={[
      { width: 100, height: 3, backgroundColor: 'black', margin: 1 },
      style,
    ]}
  />

  const renderItems = () => {
    let output = []
    for(let i = 0; i < counter; i++) {
      output.push(
        <Animated.View
        key={i + 'a'}
          style={[
            { width: 100, height: 3, backgroundColor: 'blue', margin: 1 },
            style,
          ]}
        />
      )
    }
    return output
  }

  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
        marginTop: 30
      }}>
      <Button
        title="animate"
        onPress={() => {
          randomWidth.value = Math.random() * 350;
        }}
      />
      <Button
        title="increment counter"
        onPress={() => {
          setCounter(counter + 1)
        }}
      />
      <Button
        title="add item to static lists"
        onPress={() => {
          setCounter2(counter2 + 1)
          setItemList([...itemList, <Animated.View
            key={counter2 + 'b'}
            style={[
              { width: 100, height: 3, backgroundColor: 'green', margin: 1 },
              style,
            ]}
          />])
        }}
      />
      <Button
        title="toggle state"
        onPress={() => {
          setToggleState(!toggleState)
        }}
      />
      <Animated.View
        style={[
          { width: 100, height: 3, backgroundColor: 'orange', margin: 1 },
          style,
        ]}
      />
      {toggleState && <Animated.View
        style={[
          { width: 100, height: 3, backgroundColor: 'black', margin: 1 },
          style,
        ]}
      />}
      {toggleState && staticObject}
      {renderItems()}
      {itemList}
    </View>
  );
}

```

</details>

### code2

Still works

https://user-images.githubusercontent.com/36106620/149800303-4c4316aa-7765-4c66-a81a-74489d9f0215.mov

<details>
<summary>code2</summary>

```js
import React from 'react';
import { View } from 'react-native';
import Animated, {
  useSharedValue,
  withSpring,
  useAnimatedStyle,
  useAnimatedGestureHandler,
  interpolate,
  Extrapolate,
  runOnJS,
} from 'react-native-reanimated';
import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import { useEffect, useState } from 'react';

function DragAndSnap(): React.ReactElement {
  const translation = {
    x: useSharedValue(0),
    y: useSharedValue(0),
  };
  type AnimatedGHContext = {
    startX: number;
    startY: number;
  };

  // run a couple of updates when gesture starts
  const [counter, setCounter] = useState(0);
  const makeFewUpdates = () => {
    let countdown = 100;
    const doStuff = () => {
      setCounter(countdown);
      countdown--;
      if (countdown > 0) {
        requestAnimationFrame(doStuff);
      }
    };
    doStuff();
  };

  const gestureHandler = useAnimatedGestureHandler<
    PanGestureHandlerGestureEvent,
    AnimatedGHContext
  >({
    onStart: (_, ctx) => {
      ctx.startX = translation.x.value;
      ctx.startY = translation.y.value;
      runOnJS(makeFewUpdates)();
    },
    onActive: (event, ctx) => {
      translation.x.value = ctx.startX + event.translationX;
      translation.y.value = ctx.startY + event.translationY;
    },
    onEnd: (_) => {
      translation.x.value = withSpring(0);
      translation.y.value = withSpring(0);
    },
  });

  const stylez = useAnimatedStyle(() => {
    const H = Math.round(
      interpolate(translation.x.value, [0, 300], [0, 360], Extrapolate.CLAMP)
    );
    const S = Math.round(
      interpolate(translation.y.value, [0, 500], [100, 50], Extrapolate.CLAMP)
    );
    const backgroundColor = `hsl(${H},${S}%,50%)`;
    return {
      transform: [
        {
          translateX: translation.x.value,
        },
        {
          translateY: translation.y.value,
        },
      ],
      backgroundColor,
    };
  });

  // make render slower
  let f = 0;
  for (var i = 0; i < 1e8; i++) {
    f++;
  }

  return (
    <View style={{ flex: 1, margin: 50 }}>
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View
          style={[
            {
              width: 40,
              height: 40,
            },
            stylez,
          ]}
        />
      </PanGestureHandler>
    </View>
  );
}

export default DragAndSnap;

```

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

initialUpdaterRun causes animated style to occasionally flicker due to rerenders

2 participants