error: Attempted to assign to read only property on using Animated react native - reactjs

For some reason I'm not able to see what I'm doing wrong with my code. I seem to be using Animated just as the documentation shows but this error keeps coming.
The code snippet:
import React, {
Component
} from 'react';
import {
StyleSheet,
Image,
Animated,
} from 'react-native'
import Header from './../components/Header'
export default class DrawerShell extends Component {
constructor(props) {
super(props)
this.state = {
showNav: false,
}
this.anim = new Animated.Value(0)
this.openDrawer = this.openDrawer.bind(this)
}
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
}) !this.state.showNav ? toValue = 1 : toValue = 0
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}
render() {
let {
showNav
} = this.state
return ( <
Animated.View style = {
[
styles.appContainer,
{
transform: [{
translate: [
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
}),
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 80],
}),
0
]
},
{
scale: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.7]
})
}
]
},
]
} >
<
Image source = {
{
uri: "splash_bg"
}
}
style = {
styles.bgImage
} >
<
Header title = "hi there"
onPress = {
this.openDrawer
}
/> <
/Image>
</Animated.View>
);
}
}

Might be useful for others coming from Google. Be sure you're using animated values within animated components like Animated.View. Often overlooked when 'upgrading' a view to be animated.

Figured. Two reasons why this error was being thrown.
I was interpolating the same value multiple times. Which is not allowed.
Setting state would call interpolate once more. Which was not required.
Once I Stopped doing interpolate multiple times on the same value and made it independent of state, the error went away.

For those who come here, you need to have animated values inside <Animated.View>!
Refer to this issue:
https://github.com/facebook/react-native/issues/10716#issuecomment-258098396

i think this might work you assign value directly to the state object in returnary operator and that case the error
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
})
toValue = (!this.state.showNav) ? 1 : 0 // change this this line case error
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}

In my case I found the animated transform: [{ scale: ...}] value needs to be applied through a style property rather than directly to the view.
This is the working, not-animated code I started from:
<View transform={[{ scale: 0.8 }]}>
...
</View>
But this throws the attempted to assign to read-only property exception:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View transform: { scale: animVal }>
...
</Animated.View>
This works!:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View style={{ transform: [{ scale: animVal }]}}>
...
</Animated.View>
(This is not exactly the problem the question holder had but it may help others who stumble upon this through Google)

Related

How to make a Header that Animates from Transparent to Opaque Color on Scrolling down in React-Native React Navigation 5?

I'm trying to make header that will animate from transparent to solid opaque color upon scrolling down using in React-Native React Navigation 5.
Starts to transition to opaque when scrolling halfway
Becomes fully opaque when reach the maximum offset
You can do this by setting the header style opacity to an animated value.
First define your animated value, we'll interpolate the yOffset to get the opacity desired.
const yOffset = useRef(new Animated.Value(0)).current;
const headerOpacity = yOffset.interpolate({
inputRange: [0, 200],
outputRange: [0, 1],
extrapolate: "clamp",
});
then you want to attach an animated.event listener to an animated scroll view
<Animated.ScrollView
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: yOffset,
},
},
},
],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
Your content should be inside the scroll view
In your screen add a on mount or use effect where you set the animatedValue as the header opacity
useEffect(() => {
navigation.setOptions({
headerStyle: {
opacity: headerOpacity,
},
headerBackground: () => (
<Animated.View
style={{
backgroundColor: "white",
...StyleSheet.absoluteFillObject,
opacity: headerOpacity,
}}
/>
),
headerTransparent: true,
});
}, [headerOpacity, navigation]);
I've used header transparent and header background so that the background component changes also.
Here is an example:
https://snack.expo.io/#dannyhw/react-navigation-animated-header
const handleScroll = () => {
YourElementRef.current.style.backgroundColor = `rgba(245, 245, 245, ${window.scrollY > 300 ? 1 : '0.' + (window.scrollY * 3)})`;
}
window.addEventListener('scroll', handleScroll, true);
You need to add scroll listener and call function that animates it.
The scroll element is represented by a ref stuff. e.g.
const YourElementRef = React.useRef(null);
<SomeElement ref={YourElementRef}...

React Native memory leak react native gesture handler

My apps memory usage is increasing by 0,1 MB about every 3 seconds without me doing anything in the app. I made sure to remove all event listeners so that's not the problem, im out of tricks to solve this memory leak. Is there a tool to inspect which processes are writing to the ram or some other way to detect this leak ?
I have detected the memory leak, it was an issue with react-native-gesture-handler, I did this:
<PanGestureHandler
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}>
<Animated.View style={{ transform: [{ translateX: this.translateX }] }}>
<FlatList />
</Animated.View>
</PanGestureHandler>
I didn't thought about that there is going to be a gesture handler in front of the whole FlatList which in my case contains 200+ items. I still don't ge why the memory usage is increasing without doing anything but I have resolved this issue.
This is my workaround:
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const TOSS_SEC = 0.2;
const MULTIPLIER = Math.round(SCREEN_WIDTH / 90);
class ReanimatedFlatList extends React.Component {
constructor(props) {
super(props);
// drag Distance
const dragX = new Value(0);
// gesture state
const state = new Value(-1);
// drag velocity
const dragVX = new Value(0);
this.onGestureEvent = event([
{ nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } },
]);
this.transX = new Value();
const prevDragX = new Value(0);
const clock = new Clock();
const snapPoint = cond(
lessThan(add(this.transX, multiply(TOSS_SEC, dragVX)), -80),
-100,
0,
);
this.unconstrainedX = cond(
eq(state, State.ACTIVE),
[
stopClock(clock),
set(this.transX, add(this.transX, sub(dragX, prevDragX))),
set(prevDragX, dragX),
this.transX,
],
[
set(prevDragX, 0),
set(
this.transX,
cond(
defined(this.transX),
runSpring(clock, this.transX, dragVX, snapPoint),
0,
),
),
],
);
this.translateX = interpolate(this.unconstrainedX, {
inputRange: [-100, 0],
outputRange: [-100, 0],
extrapolate: Extrapolate.CLAMP,
});
}
render() {
return (
<React.Fragment>
<Animated.View style={{ transform: [{ translateX: this.translateX }] }}>
<FlatList />
</Animated.View>
<PanGestureHandler
maxPointers={1}
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onGestureEvent}>
<Animated.View
style={{
transform: [{ translateX: multiply(this.translateX, MULTIPLIER) }],
position: 'absolute',
top: 0,
width: SCREEN_WIDTH,
right: -SCREEN_WIDTH + 50,
bottom: 0,
}}
/>
</PanGestureHandler>
</React.Fragment>
);
}
}
This allows me to move the FlatList 100 pt to the left and display something next to it much like a navigation drawer. This solution is not perfect because you are not going to be able to scroll between SCREEN_WIDTH - 50 pt and SCREEN_WIDTH on the x axis but I haven't found a better solution for now.

Pull Scrollview to reveal View - React Native

I'm trying to build something similar to IMessage's and WhatsApp's header in react native, where users can pull down to reveal a search bar in the header.
I have been able to pull down to reveal a hidden input, but because the scrollview's y value becomes negative on pull, it will bounce back to y = 0 and prevent the input from sticking to the top. I have tried using both translateY and scaleY to reveal the hidden input.
class List extends Component {
scrollY = new Animated.Value(0)
render() {
const translateY = this.props.scrollY.interpolate({
inputRange: [ -50, 0 ],
outputRange: [ 50, 0 ],
extrapolate: 'clamp',
})
return (
<>
<Animated.View style={[
styles.container,
{ transform: [ { translateY } ] },
]}>
<Input />
</Animated.View>
<Animated.ScrollView
onScroll={Animated.event(
[ { nativeEvent: { contentOffset: { y: this.scrollY } } } ],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
{...}
</Animated.ScrollView>
</>
)
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.white,
width: windowWidth,
height: 50,
position: 'absolute',
top: -50,
zIndex: -99,
},
});
I found this Stack Overflow post that has been useful to reference but it is IOS specific Pull down to show view
I solved this by using contentOffset and without any animations. I needed to make sure the scrollview was at least the size of the phone's windowHeight and then used contentOffset to push the initial y value of the Scrollview to the size of the header
<ScrollView
ListHeaderComponent={() => (
<Header headerHeight={hiddenHeaderHeight} />
)}
contentContainerStyle={{ minHeight: windowHeight }}
contentOffset={{ y: hiddenHeaderHeight }}
...
This solution works for a Flatlist as well.
One thing to note is contentOffset is an ios specific prop
check out this medium article. It provides a detailed explanation of how to do something similar to your desired behavior.

How to use react-spring from react component classes

I am trying to import react-spring animation library to a reactjs application which is based on react component classes.
It seems that new (as of 2019) React Hooks made some integration messier.
So that is why I am asking how to use react-spring which in turn uses react hooks, in a ReactJS application what uses classes.
The code that does not work properly looks like:
import React from 'react';
import { useSpring, animated, interpolate } from 'react-spring'
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
o: 1,
xyz: [10, 20, 5],
color: 'green'
});
this.aniText = <animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// You can also form ranges, even chain multiple interpolations
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
// Interpolating strings (like up-front) through ranges is allowed ...
borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>
};
render() {
return <div>
{this.aniText}
</div>;
}
}
which results this error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
You can't use hooks inside class components. So, you could either split out the animated component into its own functional component, which would look like this:
import React from 'react';
import { useSpring, animated, interpolate } from 'react-spring'
const AniText = ()=> {
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
o: 1,
xyz: [10, 20, 5],
color: 'green'
});
return (<animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// You can also form ranges, even chain multiple interpolations
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
// Interpolating strings (like up-front) through ranges is allowed ...
borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>)
}
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<AniText />
</div>;
}
}
OR, if you want to stick with a class component, react-spring exports a render-props API as well, which is completely valid inside any React component, class or otherwise:
import React from "react";
import { Spring, animated, interpolate } from "react-spring/renderprops";
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Spring
native
from={{ o: 0, xyz: [0, 0, 0], color: "red" }}
to={{ o: 1, xyz: [10, 20, 5], color: "green" }}
>
{({ o, xyz, color }) => (
<animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate(
[o, color],
(o, c) => `${o * 10}px solid ${c}`
),
// You can also form ranges, even chain multiple interpolations
padding: o
.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] })
.interpolate(o => `${o}%`),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{// Finally, this is how you interpolate innerText
o.interpolate(n => n.toFixed(2))}
</animated.div>
)}
</Spring>
</div>
);
}
}
Here is a codesandbox with the two solutions side-by-side:
https://codesandbox.io/s/8ynxyowzk0

How to scale the size of a button when the user holds down in Animated React / React Native?

I'm trying to create some code that increases the size of a button when the user holds it down then reverts back to the initial size when released.
I'm relatively new to React/RN and have searched tons of websites to find the result, but can't seem to find anything.
I can't tell whether I should be using PanResponder here or not. I also tried using Animated.timing, but the timing is hard-coded & not bound to the length of time that the user holds down the button. I tried Animated.spring, but again that's not bound to length of time that the user holds the button down.
I'll post a quick gif that replicates what I'm trying to go for.
https://imgur.com/a56pSQl
Here's what I have so far:
this.scaleAnimation = new Animated.value(3)
handlePress = () => {
Animated.spring(this.scaleAnimation, {
toValue: 4,
friction: 2,
tension: 160
}).start()
}
render() {
const pauseStyle = {
transform: [
{ scale: this.scaleAnimation }
]
}
return (
<TouchableWithoutFeedback onPress={this.handlePress}>
<Animated.View style={[ pauseStyle ]}>
<Ionicons name="md-pause" />
</Animated.View>
</TouchableWithoutFeedback>
)
}
Any takes are greatly appreciated :D
Please find the detailed answer with code snippets.
Add this to constructor of component
this.handlePressIn = this.handlePressIn.bind(this);
this.handlePressOut = this.handlePressOut.bind(this);
this.animatedValue = new Animated.Value(1);
This method is for scaling button with animation when button gets pressed
handlePressIn() {
Animated.spring(this.animatedValue, {
toValue: 3,
friction: 3,
tension: 40
}).start();
}
This method is for resetting button to its initial scale with animation when touch gets released
handlePressOut() {
Animated.spring(this.animatedValue, {
toValue: 1,
friction: 3,
tension: 40
}).start();
}
Render method
render() {
const animatedStyle = {
transform: [{ scale: this.animatedValue }]
}
return (
<TouchableWithoutFeedback
onPressIn={this.handlePressIn}
onPressOut={this.handlePressOut}
>
<Animated.View style={animatedStyle}>
<Text style={styles.text}>Press Me</Text>
</Animated.View>
</TouchableWithoutFeedback>
);
}
You can use different types of animations. Please find the link for reference:
https://facebook.github.io/react-native/docs/animated#configuring-animations

Resources