Show default element while loading Image - react-native

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.

Related

React Native Inline function understanding

Below are 2 patterns of code from a simple react native class component. Is there any performance difference in those? The difference is in the way a function called on an event of native control. If there is a performance difference, I want to know how to check and verify actually there is performance difference.
Pattern 1:-
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
}
}
onNameChange = (text) => {
this.setState({ name: text });
}
render() {
const { name } = this.state;
return (
<TextInput onChangeText={this.onNameChange} value={name} />
)
}
}
Pattern 2:-
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
}
}
onNameChange = (text) => {
this.setState({ name: text });
}
render() {
const { name } = this.state;
return (
<TextInput onChangeText={(text) => {
this.onNameChange(text);
}} value={name} />
)
}
}
If there is a performance difference then I need to adopt to the first pattern.
I think that there is no performance difference since the code gets transformed or minified before it gets executed.
There is no performance difference in the two patterns that you have mentioned.
First one just passes the reference to execute.
Second is just the wrapper (anonymous function) to execute your actual function.

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

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
}
}

React Native : Conditional render() based on AsyncStorage result

Trying to use a AsyncStorage variable to conditionally render content.
My app uses createBottomTabNavigator from react-navigation. I have a tab called Settings that must conditionally render content based on wether a user is logged in or not (checking AsyncStorage). The following code works on first render but another tab can update AsyncStorage value, returning back to Settings tab it still renders initial content.
Which approach can i use to achieve this, i'm also trying to use shouldComponentUpdate but i'm not sure how it works.
import React, {Component} from 'react';
class Settings extends React.Component{
constructor(props){
super(props);
this.state = {
isLoggedIn:false
};
}
//I want to use this method but not sure how.
shouldComponentUpdate(nextProps, nextState){
// return this.state.isLoggedIn != nextState;
}
componentDidMount(){
console.log("componentWillUpdate..");
this.getLocalStorage();
}
getLocalStorage = async () => {
try {
const value = await AsyncStorage.getItem('username');
if(value !== null) {
this.setState({isLoggedIn:true});
}
} catch(e) {
// error reading value
}
}
render() {
if(this.state.isLoggedIn)
{
return(
<View>
<Text style={styles.title_header}>Logged In</Text>
</View>
);
}
else{
return(
<View>
<Text style={styles.title_header}>Logged Out</Text>
</View>
);
}
}
}
export default Settings;
})
Use NavigationEvents. Add event listeners to your Settings components.
onWillFocus - event listener
onDidFocus - event listener
onWillBlur - event listener
onDidBlur - event listener
for example, the following will get fired when the next screen is focused.
focusSubscription = null;
onWillFocus = payload => {
// get values from storage here
};
componentDidMount = () => {
this.focusSubscription = this.props.navigation.addListener(
'willFocus',
this.onWillFocus
);
};
componentWillUnmount = () => {
this.focusSubscription && this.focusSubscription.remove();
this.focusSubscription = null;
};
The problem comes from react-navigation createBottomTabNavigator. On first visit, the component is mounted and so componentDidMount is called and everything is great.
However, when you switch tab, the component is not unmounted, which means that when you come back to the tab there won't be any new call to componentDidMount.
What you should do is add a listener to the willFocus event to know when the user switches back to the tab.
componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', () => {
AsyncStorage.getItem('username').then((value) => {
if (value !== null) {
this.setState({ isLoggedIn: true });
}
catch(e) {
// error reading value
}
});
});
}
Don't forget to remove the listener when the component is unmounted:
componentWillUnmount() {
this.listener.remove();
}

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}}
/>