How to use react-spring from react component classes - reactjs

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

Related

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.

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

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)

React-leaflet how to resetStyle

I'm following Leaflet's Choropleth tutorial
http://leafletjs.com/examples/choropleth.html
and using react-leaflet.
I managed to setStyle without any modification from the original source code and it works.
highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
}
The layer has a setStyle property. Now to resetStyle that I'm having propblems.
I tried to access it with
resetHighlight(e) {
this.refs.geojson.resetStyle(e.target);
}
while having GeoJson
<GeoJson
ref="geojson"
data={this.state.data}
style={this.getStyle.bind(this)}
onEachFeature={this.onEachFeature.bind(this)}
/>
but it it doesn't have resetStyle property
Anyone can suggest another way of resetting the style in react-leaflet ?
The solution was to access the leafletElement of geojson which has resetStyle
resetHighlight(e) {
this.refs.geojson.leafletElement.resetStyle(e.target);
}
react-leaflet-choropleth is a way to handle choropleth if you are not wanting to write it from scratch. It is based off of the leaflet-choropleth plugin
import Choropleth from 'react-leaflet-choropleth'
import { Map } from 'react-leaflet'
const style = {
fillColor: '#F28F3B', //default color filll
weight: 2, //normal styling
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.5
}
const map = (geojson) => (
<Map>
<Choropleth
data={{type: 'FeatureCollection', features: geojson} /*feature collection or array*/}
valueProperty={(feature) => feature.properties.value /*value for choropleth*/}
visible={(feature) => feature.id !== active.id /*use choropleth color?*/}
scale={['#b3cde0', '#011f4b'] /*color range*/}
steps={7 /*how many different colors to use?*/}
mode={'e' /*use equadistance mode, others include kmeans and quantile*/}
style={style}
onEachFeature={(feature, layer) => layer.bindPopup(feature.properties.label)}
ref={(el) => this.choropleth = el.leafletElement /*get the geojson's layer container*/}
/>
</Map>
)
ReactDom.render(<map geojson={...} />, document.body)

One material card, Multiple animations using react-addons-transition-group

I have a <Card> component from material-ui. It has a CardHeader, CardText and other pieces that come together to make the whole card.
I am able to use react-addons-transition-group to animate this card as it enters the screen (componentWillEnter). This works great with:
export class MyCard extends React.Component {
constructor(props) {
super(props);
}
componentWillEnter (callback) {
const el = findDOMNode(this);
TweenMax.fromTo(el, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
render(){
return <Card>
<h1> Blah Blah Title <h1>
<CardText> Blah Blah </CardText>
</Card>
}
}
Then the above gets rendered in another component like so:
<ReactTransitionGroup> <MyCard> </ReactTransitionGroup>
Wrapping it in ReactTransitionGroup makes sure it calls the componentWillEnter gets called, and the animation runs. This works as expected.
What I would like to do is multiple animations. So, the Card comes in from the left, the CardText comes in from the top, and the h1 comes in from the right-- or something to that affect.
Ideally, I would just do something like:
componentWillEnter (callback) {
const el = findDOMNode(Card);
const el = findDOMNode(h1);
const el = findDOMNode(CardText);
TweenMax.fromTo(Card, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(h1, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(CardText, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
Of course, this doesn't work.
So my question is, how do I go about parsing the this instance of the component so I can animate each individual part?
I couldn't really find much on findDOMNode. It doesn't seem I can add id tags to the different parts and use those in findDOMNode. Or maybe I'm going about it all wrong.
Found the answer! Simply use refs and access them in the this object:
So:
render(){
return <Card ref="card">
<h1 ref="title"> Blah Blah Title <h1>
<CardText ref="text"> Blah Blah </CardText>
</Card>
}
Then you reference them in the animation portion like:
export class MyCard extends React.Component {
constructor(props) {
super(props);
}
componentWillEnter (callback) {
const card = findDOMNode(this);
const title = findDOMNode(this.refs.title);
const description = findDOMNode(this.refs.description);
TweenMax.fromTo(card, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(title, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(description, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
render(){
return <Card>
<h1 ref="title"> Blah Blah Title <h1>
<CardText ref="text"> Blah Blah </CardText>
</Card
}
}
You'll of course want to differ the timing and animations in each TweenMax.fromTo call but that should do it!
I guess it is worth adding some further reading, as string refs may get deprecated? Hard to tell:
https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#pending-change-the-refs-semantics
https://twitter.com/dan_abramov/status/664212109056729088

Resources