Binding on finish callback after Animated.Value has started animating? - react-native

I'm in the midst of learning React Native's Animated library, but have stumbled across a problem for which I'm sure there must be a solution, but can't seem to find it.
Context:
I have a component which creates a new Animated.Value and starts animating it. I then want to pass that single value to sub components so they can all animate in their own away against this canonical value. But I also want some of these sub components to handle the on finished event.
Currently:
The only way the docs explain handling the animation on finished event is by passing a callback function to .start(). But the parent component which starts the animation doesn't know how the multiple sub components intend to handle it.
Question:
Is there anyway to bind this callback after the animation has started? Something like this.props.animatingValue.addOnFinished(...) would be nice :)
Please let me know if I'm misunderstanding something fundamental. Is it a bad idea to pass Animated.Value instances as props? If so, what's a better way to approach this problem?
Thanks!
EDIT (Request for code example):
I'm just making this up as I go, so please forgive any syntax errors, but this should demonstrate what I'm trying to do:
class Parent extends React.Component {
componentDidMount() {
this.setState({
animatedValue: new Animated.Value(0)
}, () => {
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 1000
}).start()
})
}
render() {
return this.state.animatedValue ? (
<View>
<ChildOne animatedValue={this.state.animatedValue} />
<ChildTwo animatedValue={this.state.animatedValue} />
<ChildThree animatedValue={this.state.animatedValue} />
</View>
) : null
}
}
class ChildOne extends React.Component {
componentDidMount() {
// What I'd like to do...
this.props.animatedValue.onFinished(() => { /* ... something ... */ }
}
render() { /* interpolate some style against the animation, not important */ }
}
// same for ChildTwo and ChildThree ...

Something like this would work. Use this approach in all your children.
class Child extends React.Component {
componentDidMount() {
this.props.addAnimationEndedListener(this.onAnimationEnded);
}
onAnimationEnded = (finished)=>{
/*The animation has Ended. Do what you want in the child here*/
};
}
And then in parent,
class Parent extends React.Component {
componentDidMount() {
this.setState({
animatedValue: new Animated.Value(0)
}, () => {
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 1000
}).start(this.onAnimationEnded)
});
this.animationListeners = [];
}
addAnimationEndedListener = (listener)=>{
this.animationListeners.push(listener);
}
onAnimationEnded = ({finished})=>{
this.animationListeners.forEach(notifyListener=>{
notifyListener(finished);
});
}
render() {
return this.state.animatedValue ? (
<View>
<ChildOne animatedValue={this.state.animatedValue} addAnimationEndedListener={this.addAnimationEndedListener} />
<ChildTwo animatedValue={this.state.animatedValue} addAnimationEndedListener={this.addAnimationEndedListener} />
<ChildThree animatedValue={this.state.animatedValue} addAnimationEndedListener={this.addAnimationEndedListener} />
</View>
) : null
}
}

Related

Reload screen when timeout occurs

I am about to improve my first react native app and I want to show a retry/reload button when a timeout occurs. So for instance the webservice is not responding within 5 seconds I am throwing an issue. But now I want to give the opportunity to reload the screen.
I already tried to create a refresh method and use setState, but this did not work. I think I need to start the render method once again?
render() {
if (this.state.connection === true) {
return (
<Login />
)
else {
<View style={styles.errorConnection}>
<Text>Error, no Connection</Text>
<Button title="reload" />
</View>
}
}
...
componentDidMount() {
this.checkConnection();
}
checkConnection = async () => {
let x = await new connection().checkConnection();
this.setState(connection:x);
}
You didn't ask a specific question, I cannot give a specific answer but here you go. Feel free to ask in the comments :)
class SomeComponent extends React.Component {
state = {
requestFailed: false,
}
constructor(props) {
super(props);
// This line is very important if you don't know why ask
this.handleRetry = this.handleRetry.bind(this);
}
render() {
return (
<>
<Text>This is some very important stuff here</Text>
{this.state.requestFailed && (
<Button title="Try Again" onPress={this.handleRetry}
)}
</>
)
}
handleRetry() {
this.fetch();
}
fetch() {
fetch('your.url.url')
.then()
.catch(() => this.setState({requestFailed: true}))
}
}

Animated API's createAnimatedComponent makes FlatList's ref undefined in react-native

I'm trying to use scrollToIndex function of FlatList on react-native. However, when I switch the FlatList to createAnimatedComponent(FlatList), its ref becomes undefined.
Is there a way to keep FlatList's ref when I use createAnimatedComponent?
Thank you for your concern.
currently createAnimatedComponent exposes a method called getNode() that should work for getting the ref to the underlying component.
Here are 2 examples, one with the old refs and one with the new
// old ref style
class DemoComp extends Component {
componentDidMount() {
// setTimeout is needed for scrollToOffset ¯\_(ツ)_/¯
setTimeout(() => {
this.listRef.getNode().scrollToOffset({ offset: 100, animated: true });
}, 0);
}
render() {
return <Animated.FlatList ref={r => { this.listRef = r; }} {...otherProps} />;
}
}
// new ref style
class DemoComp extends Component {
listRef = React.createRef();
componentDidMount() {
// setTimeout is needed for scrollToOffset ¯\_(ツ)_/¯
setTimeout(() => {
this.listRef.current.getNode().scrollToOffset({ offset: 100, animated: true });
}, 0);
}
render() {
return <Animated.FlatList ref={this.listRef} {...otherProps} />;
}
}
Eventually, in the future createAnimatedComponent will switch to "forwarded refs" which will only work with the new style. But that day is a long way away because of all the libraries that depend on the old style.
PS. If you're reading this in the distant future, you can check on the status of forwarded refs in createAnimatedComponent here: https://github.com/facebook/react-native/issues/19650

how can i set a state variable as component's property in react native?

i started learning react native and building an android app.so i have facing some issue with setting and getting a component's property.here is my code
i have two components named as content-container and bar-chart.
inside content-container ,here is my code block:
state = {
barChartResponse: {},
arcChartResponse: {},
stackChartResponse: {},
lineChartResponse: {},
token:'abc',
};
componentWillMount() {
this.setState({token:'xyz'});
}
render() {
return (
<ScrollView>
<BarChart chartData = {this.state.token} />
</ScrollView>
);
}
now i am trying to get this property inside bar-chart component as follows:
constructor(props) {
super(props);
Alert.alert("ChartData is : ",props.chartData);
}
it displays me value what i set in state object by default i.e. abc, but i want updated value.
please help me to find out what am i doing wrong....... thanks in advance.
You can use componentWillRecieveProps but it is deprecated and in RN>54 you can use componentDidUpdate or getDerivedStateFromProps to get state from parent like this:
componentDidUpdate(nextProps){
if (this.props.chartData !== nextProps.chartData) {
alert(nextProps.chartData)
}
}
or
static getDerivedStateFromProps(props, current_state) {
if (current_state.chartData !== props.chartData) {
return {
chartData: props.chartData,
}
}
}
You need to update state of parent component it will automatically reflect in child component but next time you will receive in componentWillRecieveProps(nextProps) then render method.
for example:
state = {
barChartResponse: {},
arcChartResponse: {},
stackChartResponse: {},
lineChartResponse: {},
token:'abc',
};
componentWillMount() {
this.setState({token:'xyz'});
}
updateState = () => {
this.setState({token: "newToken"})
}
render() {
return (
<ScrollView>
<Button onPress={this.updateState}>update State</Button>
<BarChart chartData = {this.state.token} />
</ScrollView>
);
}
in BarChart.js
componentWillRecieveProps(nextProps) {
// you can compare props here
if(this.props.chartData !== nextProps.chartData) {
alert(nextProps.chartData)
}
}

How do I alternate two Images in a react native component

I'm trying to animate an icon in React native by simply switching the image every 500ms. My code looks like this:
export default class FlashingIcon extends Component {
constructor(props) {
super(props);
this.state = {
on: true,
};
setInterval(() => {
this.setState(previousState => {
return {
on: !previousState.on,
};
});
}, 500);
}
render() {
let sprite = this.state.on
? require('../onIcon.png')
: require('../offIcon.png');
return (
<Image
source={sprite}
style={{width:16, height:20}}
/>
);
}
}
The code is basically copy-and-pasted from:
https://facebook.github.io/react-native/docs/state.html and
https://facebook.github.io/react-native/docs/images.html
Each image shows up if I just copy the require into the <Image>. I can also verify that if I instead render a <Text> element outputting this.state.on, it shows the correct value alternating.
I can't for the life of me work out what I've done wrong.
Add key to Image It will help in re-rendering image once state changed.
<Image
key={this.state.on}
source={sprite}
style={{width:16, height:20}}
/>

Show default element while loading Image

I have a component representing an user avatar that loads an image from my API.
I want it to display a default avatar (not another image) while the avatar is loading.
constructor() {
super();
this.state = {
loaded: false,
};
}
render() {
if (!this.props.uri || !this.state.loaded) {
return (
<DefaultAvatar />
);
}
return <Image onLoad={this.onLoad.bind(this)} uri={this.props.uri} />;
}
onLoad() {
this.setState({loaded: true});
}
The problem I have is that with this current code, the Image will never be rendered, so the state will never change. I'm unable to find a solution that would satisfy React principles and my requirements (no ghost components to load the image before displaying it).
class LazyImage extends React.Component{
constructor () {
super(this.props)
this.state = {loaded: false}
}
handleLoad () {
this.setState({loaded:true})
}
componentDidMount () {
this.img = new Image()
this.img.onload = this.handleLoad.bind(this)
this.img.src = this.props.src
}
render () {
return this.state.loaded?<img src={this.props.src}/>:<div>Loading...</div>
}
}
You create a native Image element and wait for it to load. Then you render the image with react. The browser is smart and fetches it from the cache this time. Instant render!
See http://jsfiddle.net/4hq3y4ra/3/ for a demo.
There are several ways this can be achieved, however to keep things simple, you can use a literal condition to toggle default avatar and the actual image.
constructor() {
super();
this.state = {
loaded: false,
};
}
onLoad(dataUri) {
if(dataUri !== undefined){
this.setState({loaded: true});
}
},
render() {
return (
<Image onLoad={this.onLoad} uri={this.state.loaded ? this.props.uri : 'default-avatar'} />
);
}
Image.prefetch will allow me to do what I want, thanks to everyone.