Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] interpolateColor - Attempt to assign to readonly property #2739

Closed
1 task done
yaroslavnikiforov opened this issue Dec 13, 2021 · 6 comments
Closed
1 task done
Assignees
Labels

Comments

@yaroslavnikiforov
Copy link

Description

When you try to render a component with some color interpolation using interpolateColor after another component with color interpolation is already rendered you get an error - Attempt to assign to readonly property.

Simulator Screen Shot - iPhone 13 - 2021-12-13 at 19 08 36

This only happens on iOS.

Expected behavior

You shouldn't get this error on iOS.

Actual behavior & steps to reproduce

Render some component that uses interpolateColor and then render another component that also uses interpolateColor in a few seconds, for example. This also works for different screens in the navigation stack.

Snack or minimal code example

Here is a reproducible demo:
https://github.com/yaroslavnikiforov/InterpolateColor/tree/main/android

Code example:

import React, {useState, useEffect} from 'react';
import {StyleSheet} from 'react-native';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  interpolateColor,
} from 'react-native-reanimated';

const styles = StyleSheet.create({
  container: {flex: 1},
});

export default function App() {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    setTimeout(() => setIsVisible(true), 3000);
  }, []);

  return (
    <>
      <BlockOne />

      {isVisible ? <BlockTwo /> : null}
    </>
  );
}

function BlockOne() {
  const animatedValue = useSharedValue(300);
  const animatedStyles = useAnimatedStyle(() => {
    const backgroundColor = interpolateColor(
      animatedValue.value,
      [0, 234, 468],
      ['red', 'orange', 'yellow'],
    );

    return {backgroundColor};
  }, [animatedValue]);

  return <Animated.View style={[styles.container, animatedStyles]} />;
}

function BlockTwo() {
  const animatedValue = useSharedValue(0.3);
  const animatedStyles = useAnimatedStyle(() => {
    const backgroundColor = interpolateColor(
      animatedValue.value,
      [0, 1],
      ['green', 'cyan'],
    );

    return {backgroundColor};
  }, [animatedValue]);

  return <Animated.View style={[styles.container, animatedStyles]} />;
}

Package versions

  • React Native: 0.66.4 (the same with 0.63.4)
  • React Native Reanimated: ^2.3.0
  • NodeJS: 14.15.0
  • Xcode: 13.1
  • Java & Gradle: 15.0.2 & 6.7.1

Affected platforms

  • iOS
@github-actions
Copy link

Issue validator

The issue is valid!

@github984527
Copy link

how to fix?

@samjusaitis
Copy link

I am also experiencing this after upgrading to Reanimated 2.3

@MingaudasVagonis
Copy link

MingaudasVagonis commented Dec 21, 2021

Same as #2329.
It seems like enabling hermes fixes that (#2329 (comment)), if that's not an option for you, you can apply a patch (orig - #2329 (comment)) until the issue is resolved, didn't notice any issues in production.

diff --git a/node_modules/react-native-reanimated/src/reanimated2/Colors.ts b/node_modules/react-native-reanimated/src/reanimated2/Colors.ts
index 66e5c13..fc1d52d 100644
--- a/node_modules/react-native-reanimated/src/reanimated2/Colors.ts
+++ b/node_modules/react-native-reanimated/src/reanimated2/Colors.ts
@@ -694,11 +694,6 @@ const getInterpolateCacheRGBA = (
   colors: readonly (string | number)[]
 ): InterpolateCacheRGBA => {
   'worklet';
-  const hash = colors.join('');
-  const cache = interpolateCacheRGBA[hash];
-  if (cache !== undefined) {
-    return cache;
-  }
 
   const r = [];
   const g = [];
@@ -715,15 +710,7 @@ const getInterpolateCacheRGBA = (
       a.push(opacity(proocessedColor));
     }
   }
-  const newCache = { r, g, b, a };
-  const overrideHash = hashOrderRGBA[curentHashIndexRGBA];
-  if (overrideHash) {
-    delete interpolateCacheRGBA[overrideHash];
-  }
-  interpolateCacheRGBA[hash] = newCache;
-  hashOrderRGBA[curentHashIndexRGBA] = hash;
-  curentHashIndexRGBA = (curentHashIndexRGBA + 1) % BUFFER_SIZE;
-  return newCache;
+  return { r, g, b, a };
 };
 
 interface InterpolateCacheHSV {

@yaroslavnikiforov
Copy link
Author

@MingaudasVagonis thanks! This patch works.

@piaskowyk piaskowyk self-assigned this Dec 28, 2021
@piaskowyk
Copy link
Member

Duplicate of: #2329 I will try to fix it soon. Please be patient 🙏

piaskowyk added a commit that referenced this issue Jan 27, 2022
## Description

The color cache was mutable only from the JS side. I decided to remove the cache and instead of this I created a new faster function with a better cache mechanism without previous limitations.

Performance comparation between `interpolateColor` and `interpolateSharableColor`:
```
old 2894ms - new 1741ms // 39.8% faster
old 2720ms - new 1697ms // 37.6% faster
old 2739ms - new 1650ms // 39.7% faster
old 2738ms - new 1781ms // 34.9% faster
old 2727ms - new 1662ms // 39.0% faster
```
Performance test code:
```js
const interpolateConfig = useInterpolateConfig([0, 350], ['#ff0000', '#00ff00']);
const v1 = interpolateSharableColor(randomWidth.value, interpolateConfig);
const iterations = 10000;
const t1_start = Date.now();
for(let i = 0; i < iterations; i++) {
  const v = interpolateSharableColor(randomWidth.value, interpolateConfig);
}
const t1_end = Date.now();
const t2_start = Date.now();
for(let i = 0; i < iterations; i++) {
  const v = interpolateColor(randomWidth.value, [0, 350], ['#ff0000', '#00ff00']);
}
const t2_end = Date.now();
console.log(t1_end - t1_start, t2_end - t2_start);
```

## Usage
```js
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  interpolateSharableColor,
  useInterpolateConfig,
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React from 'react';

function AnimatedStyleUpdateExample(): React.ReactElement {
  const randomWidth = useSharedValue(10);
  const interpolateConfig = useInterpolateConfig([0, 350], ['#ff0000', '#00ff00']); // <- here

  const style = useAnimatedStyle(() => {
    return {
      width: randomWidth.value,
      backgroundColor: interpolateSharableColor(randomWidth.value, interpolateConfig) // <- here
    };
  });

  return (
    <View style={{ flex: 1, flexDirection: 'column', }}>
      <Animated.View
        style={[ 
          { width: 100, height: 80, backgroundColor: 'black', margin: 30 }, 
          style 
        ]}
      />
      <Button
        title="toggle"
        onPress={() => { randomWidth.value = withTiming(Math.random() * 350) }}
      />
    </View>
  );
}

export default AnimatedStyleUpdateExample;
```

Fixes #2329 #2739
aeddi pushed a commit to aeddi/react-native-reanimated that referenced this issue Mar 22, 2022
## Description

The color cache was mutable only from the JS side. I decided to remove the cache and instead of this I created a new faster function with a better cache mechanism without previous limitations.

Performance comparation between `interpolateColor` and `interpolateSharableColor`:
```
old 2894ms - new 1741ms // 39.8% faster
old 2720ms - new 1697ms // 37.6% faster
old 2739ms - new 1650ms // 39.7% faster
old 2738ms - new 1781ms // 34.9% faster
old 2727ms - new 1662ms // 39.0% faster
```
Performance test code:
```js
const interpolateConfig = useInterpolateConfig([0, 350], ['#ff0000', '#00ff00']);
const v1 = interpolateSharableColor(randomWidth.value, interpolateConfig);
const iterations = 10000;
const t1_start = Date.now();
for(let i = 0; i < iterations; i++) {
  const v = interpolateSharableColor(randomWidth.value, interpolateConfig);
}
const t1_end = Date.now();
const t2_start = Date.now();
for(let i = 0; i < iterations; i++) {
  const v = interpolateColor(randomWidth.value, [0, 350], ['#ff0000', '#00ff00']);
}
const t2_end = Date.now();
console.log(t1_end - t1_start, t2_end - t2_start);
```

## Usage
```js
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  interpolateSharableColor,
  useInterpolateConfig,
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React from 'react';

function AnimatedStyleUpdateExample(): React.ReactElement {
  const randomWidth = useSharedValue(10);
  const interpolateConfig = useInterpolateConfig([0, 350], ['#ff0000', '#00ff00']); // <- here

  const style = useAnimatedStyle(() => {
    return {
      width: randomWidth.value,
      backgroundColor: interpolateSharableColor(randomWidth.value, interpolateConfig) // <- here
    };
  });

  return (
    <View style={{ flex: 1, flexDirection: 'column', }}>
      <Animated.View
        style={[ 
          { width: 100, height: 80, backgroundColor: 'black', margin: 30 }, 
          style 
        ]}
      />
      <Button
        title="toggle"
        onPress={() => { randomWidth.value = withTiming(Math.random() * 350) }}
      />
    </View>
  );
}

export default AnimatedStyleUpdateExample;
```

Fixes software-mansion#2329 software-mansion#2739
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants