I am developing a react-native project. I have a spinner as a custom component:
const MySpinner = ({hide = false}) => {
...
if (hide) {
return null;
} else {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Animated.View>
<MyIcon />
</Animated.View>
</View>
);
}
}
As you can see above, there is a hide property which decides whether I show MySpinner or not in parent component.
In MyScreen, I would like to show MySpinner for 5 seconds, after which show the actual content (No networking callback involved). I try to use setTimeOut function to achieve it.
This is what I tried:
const MyScreen = () {
...
return (
<View style={styles.container}>
{setTimeout(() => { <MySpinner hide={true}/>}, 3000)}
<ActualContent />
</View>
)
}
At runtime, I got error Error: Text strings must be rendered within a <Text> component.
Also, the above code doesn't show actual content after MySpinner disappear.
So, how can I achieve what I need? That's showing MySpinner at first for 5 seconds after that show the actual content.
You can achieve this in multiple ways, here is a simple example to help you get started.
Code Sandbox => https://codesandbox.io/s/wonderful-chebyshev-io6pd?file=/src/App.js
[UPDATED ANSWER]
import { useEffect, useState } from "react";
export default function App() {
const [spinner, setSpinner] = useState(true);
useEffect(() => {
setTimeout(() => {
setSpinner(false);
}, 5000);
}, []);
const component = spinner ? (
<span>Loading...</span>
) : (
<h1>Component Ready</h1>
);
return <div className="App">{component}</div>;
}
Related
I have a react native project I am working on. There are two screens. One screen is a list of properties and details that render based on an api call to zillows api. When a property is clicked on, I want it to go to a different screen with the zpid that is passed to the new screen.
On the new screen, I have two states that are set. I have a loading state that is set to true. loading will be set to true until the new api call is made and until the loading state is set to false, I want to screen to render loading text. Once the loading is set to false and the property state is set to the results of the api call, I want to render the property details on the screen.
My issue right now is that I have a useEffect right now that runs the zillow api call right when the screen loads. Once the results are stored in the property state, I set the loading to false.
When this happens I am getting an undefined variable issue that is suppsed to render the property price from the property state. I know that the issue is caused when it is trying to grab from the property state before it is loaded but I am not sure how to delay rendering the content until after the property state is set.
component:
import React, { useState, useEffect } from 'react'
import { Dimensions } from 'react-native'
import { View, Image, Text, StyleSheet, FlatList, TextInput, Button } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import { propertyOptions } from '../api/zillowApi'
import { convertToDollars } from '../utilities'
import axios from 'axios';
const PropertyDetail = (props) => {
const {
route
} = props
const [property, setProperty] = useState()
const [loading, setLoading] = useState(true)
let zpid = route.params.zpid
let deviceWidth = Dimensions.get('window').width
var aspectHeight = (deviceWidth / 1.78) + 1
let carouselImageWidth = (deviceWidth / 4)
let carouselImageHeight = (carouselImageWidth / 1.78)
let options = propertyOptions
options.params.zpid = zpid
useEffect(() => {
axios.request(options)
.then(function (response) {
// console.log("single property: ", response.data);
setProperty(response)
setLoading(false)
}).catch(function (error) {
console.error(error);
});
}, [])
const loadingContent = <Text>Loading</Text>
const loadedContent = <ScrollView>
<View style={styles.imagesContainer} className="images-container">
<View style={[styles.mainImageContainer,{height: aspectHeight}]} className="main-image-container">
<Image style={styles.mainImage} source={{uri: property.data.imgSrc}}/>
</View>
<View style={styles.carouselContainer} className="image-carousel-image">
<FlatList
horizontal
data={carouselImages}
renderItem={({item}) => <Image style={{height: carouselImageHeight, width:carouselImageWidth}} source={require('../../assets/luxury-home-1.jpeg')}/>}
/>
</View>
</View>
...
<View style={styles.taxHistoryContainer}>
<View style={styles.contactContainer}>
<Text>Listing Agent: Omar Jandali (DRE# 02151051)</Text>
<Text>Listing Brokerage: Compass (DRE# 00132433)</Text>
</View>
<View style={styles.expense}>
<TextInput placeholder='First Name'/>
<TextInput placeholder='Last Name'/>
</View>
<View style={styles.expense}>
<TextInput placeholder='Subject'/>
</View>
<View style={styles.expense}>
<TextInput placeholder='Message'/>
</View>
<Button title='Submit'/>
</View>
</ScrollView>
return(
<View>
{
loading == true ? loadingContent : loadedContent
}
</View>
)
}
I want to delay rendering the content until I know that the property state is set and I dont know how to do that...
You can do an early return, below the useEffect and your components.
(sorry if the component names are different).
if (!property) return <LoadingContent />
return (
<View>
<LoadedContent />
</View>
)
Also, you should set your loading to false in the useState.
Then when making your axios call, set it to true.
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
try {
const res = await axios.request(options);
if (res) {
setProperty(res.data);
}
} catch ...
finally {
setLoading(false);
}
}, [])
on another note, is there a reason why the initial value of your property state value is nothing? I normally would set it to null, so it's definite that it's false if there is no update to that state.
Im trying to use for my first time the Drawer Layout from react-native-gesture-handler, but everytime i try to use it wont open.
Im getting this error(both in the openDrawer):
const newBet: React.FC = () => {
let drawer: any = null;
var renderDrawer = () => {
return (
<Button onPress={drawer.closeDrawer()} title='Close Drawer'></Button>
);
};
var render = () => {
return (
<View style={{ flex: 1 }}>
<DrawerLayout
ref={(refDrawer) => {
refDrawer = refDrawer;
}}
drawerWidth={200}
drawerPosition='right'
drawerType='front'
drawerBackgroundColor='#ddd'
renderNavigationView={renderDrawer}
>
<Button onPress={drawer.openDrawer} title='Open Drawer'></Button>
</DrawerLayout>
</View>
);
};
return (
<TouchableOpacity>
<Text onPress={drawer.openDrawer()}>Render Drawer Layout</Text>)
</TouchableOpacity>
);
};
export default newBet;
You set drawer to null, and never assign a value to it. You have a typo in ref={}:
ref={(refDrawer) => {
// refDrawer = refDrawer; Assigning value to itself here :)
drawer = refDrawer
}}
Also, I'm not sure since I didn't check this but I think ref={drawer} also works.
Next, note how you sometimes write
onPress={drawer.closeDrawer()} // ending with '()'
// vs
onPress={drawer.openDrawer} // ending without '()'
You can't directly call a function or it will execute it immediately. So either write:
onPress={drawer.closeDrawer}
// or
onPress={() => drawer.closeDrawer()}
That should do the trick hopefully!
I'm making a Choose Your Own Adventure style game in React Native to learn how everything works. I figured it makes sense to keep the story in a separate JSON file and render it on one screen that updates depending on what arc the user is in.
Problem: when I write a forEach or map or other function to go through the array of paragraphs, the part of the screen it's supposed to be on is blank. Text from other parts of the return area displays fine, but what is supposed to be displayed from the forEach does not. Here's my code:
const StoryDisplayScreen = props => {
const theStory = require('../data/TestStory.json')
const theArc = 'intro'
const storyText = () => {
theStory[theArc].story.forEach((paragraph, i) => {
<Text style={styles.body}>{paragraph}</Text>
})
}
return (
<View style={styles.screen}>
<ScrollView>
<View>
{storyText()}
</View>
<View style={styles.promptNextArcArea}>
<TouchableOpacity style={styles.promptNextArc}>
<Text style={styles.promptNextArcText}>What do you do?</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
)
}
In case you're wondering the structure of the JSON file and its contents, I'm using this one to test with: https://github.com/gophercises/cyoa/blob/master/gopher.json
I've tried using map instead, tried putting things in Views, tried putting the forEach/map functions directly into the return section of the code, tried console.log-ing to confirm that my functions are working properly (which they appear to be), and... yeah, I'm at a loss.
Any suggestions?
Consider using map instead of forEach.
Full Working Example: Expo Snack
import * as React from 'react';
import {
Text,
View,
StyleSheet,
ScrollView,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
import { Card } from 'react-native-paper';
const StoryDisplayScreen = (props) => {
const theStory = require('./data/TestStory.json');
const theArc = 'intro';
const storyText = () => {
return theStory[theArc].story.map((paragraph, i) => (
<Card style={{margin: 5, padding:5}}>
<Text style={styles.body}>{paragraph}</Text>
</Card>
));
};
return (
<View style={styles.screen}>
<ScrollView>
<View>{storyText()}</View>
<View style={styles.promptNextArcArea}>
<TouchableOpacity style={styles.promptNextArc}>
<Text style={styles.promptNextArcText}>What do you do?</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
);
};
export default StoryDisplayScreen;
const styles = StyleSheet.create({
screen: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
firstly, if you want your sort is a function, it must return something, just like the render method
const sort = () => {
return (<View></View>)
}
you also can make the sort is a view, use it as a variable. like that, both they are can works.
const sort = (<View></View>)
but the sort must have something return. your way use for each and map dont return any. there are two ways can do that.
const storyText = () => {
//,first way,define a array, push it in the array
let storyViews= []
theStory[theArc].story.forEach((paragraph, i) => {
sortyviews.push(<Text style={styles.body}>{paragraph}</Text>)
})
return soortView;
}
the second way is to return directly a array
const storyText = () => {
//,first way,define a array, push it in the array
let storyViews= []
storyViews = theStory[theArc].story.map((paragraph, i) => {
return (<Text style={styles.body}>{paragraph}</Text>)
})
return soortView;
}
for more about the array operate to see the doc
I'm trying to make a list of buttons based on the input from user input type is an array of options like so multipleOptions = ['1', '2', '3'] then we loop through each option to show a Button can't figure out why it's not working here's my code :
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const InputButton = (multipleOptions, likertScale, onPress) => {
const [options, setOptions] = useState([]);
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) setOptions(multipleOptions);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
const App = () => {
return (
<View>
<InputButton multipleOptions={['1', '2','3']} />
</View>
)
}
const styles = StyleSheet.create({})
export default App;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
button: {
margin: 3,
flex: 1,
backgroundColor: '#EEF6FA',
minHeight: 72,
borderRadius: 2,
justifyContent: 'center',
alignItems: 'center',
},
});
the error message is
Too many re-renders. React limits the number of renders to prevent an infinite loop.
or sometimes this
options.map is not a function
TypeError: options.map is not a function
at InputButton
(All kind of optimisations are welcome)
Thanks in Advance guys.
code demo https://snack.expo.io/#mansouriala/dac832
You put set state in every re-render so you get a loop. So you have two options use useEffect to just set state one time or set the first state directly.
https://snack.expo.io/ZvLQM9FEF
const InputButton = ({multipleOptions, likertScale, onPress}) => {
const [options, setOptions] = useState(likertScale?[...new Array(likertScale).keys()].map((i) => i):[ ...multipleOptions]);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
export default InputButton;
You have several issues here.
The first, which leads to Too many re-renders. React limits the number of renders to prevent an infinite loop. is because you're calling setOptions at each render, which triggers another render, etc… This is infinite loop, because when you're setting a state, React re-renders the component. To avoid that, you have to wrap your expression with useEffect and the correct dependencies.
React.useEffect(() => {
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) {
setOptions(multipleOptions);
}, [multipleOptions, likertScale]);
This way, this expression would only run when multipleOptions or likertScale change.
https://reactjs.org/docs/hooks-reference.html#useeffect
The other problem is that InputButton props argument is wrong: you forgot the props destructuring. It should be const InputButton = ({ multipleOptions, likertScale, onPress }) => { /* body function */ }.
Finally, it's a bad practice to use an array index as a key prop, because array order could change. You should use a stable key, like the option value key={option}.
https://reactjs.org/docs/lists-and-keys.html#keys
I have a question. I have a loader and during the loading I would show three different texts. Like text1, then this disappear and it's show text2 and then text3.
<View style={style.container}>
<View style={style.page}>
<ActivityIndicator size="large" color="#56cbbe" />
<Text>Text1.. </Text>
<Text>Text2.. </Text>
<Text>Text3.. </Text>
</View>
</View>
In this case I only show the three texts together. How can I do?
Thank you :)
One way to solve this is to use setInterval and call update function to loop through the texts assuming if they are present in form of array.
Simply saying for example.
Let's maintain loadingText in state as loadingText: ['text1', 'text2', 'text3'],A variable to track the present item as currentLoadingTextIndex: 0 and call setInterval in componentDidUpdate.
Be careful when calling update function in here,as one wrong mistake will make your app crash.
componentDidUpdate(prevProps, prevState) {
if (!prevState.isLoading && this.state.isLoading) {
this.timerId = setInterval(this.changeLoadText, 2000);
} else if (prevState.isLoading && !this.state.isLoading) {
clearInterval(this.timerId);
}
}
and finally our update function
changeLoadText = () => {
this.setState(prevState => {
return {
currentLoadingTextIndex:
(prevState.currentLoadingTextIndex + 1) %
prevState.loadingText.length,
};
});
};
I am attaching a working expo Demo for clarity purpose.
What you want is to show indicator and text1 during loading time and then text2 and text3. Is that right?
So I made an example for you. This should solve the problem by changing the status value. You can display the indicator for the duration of loading and show the text by changing the status value when loading is complete.
Example Code
//This is an example code to understand ActivityIndicator//
import React from 'react';
//import react in our code.
import { ActivityIndicator, Button, View, StyleSheet,Text } from 'react-native';
//import all the components we are going to use.
export default class App extends React.Component {
state = { showIndicator: true };
componentDidMount() {
setTimeout(() => {this.setState({showIndicator: false})}, 2000)
}
onButtonPress = () => {
//function to change the state to true to view activity indicator
//changing state will re-render the view and indicator will appear
};
render() {
//Check if showIndicator state is true the show indicator if not show button
if (this.state.showIndicator) {
return (
<View style={styles.container}>
{/*Code to show Activity Indicator*/}
<ActivityIndicator size="large" color="#0000ff" />
<Text>Text1.. </Text>
{/*Size can be large/ small*/}
</View>
);
} else {
return (
<View style={styles.container}>
<Text>Text2.. </Text>
<Text>Text3.. </Text>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
flexDirection: 'column',
alignItems: "center"
},
});