Calling multiple functions to render - javascript

First of all, I want to explain what I want to do and after where my problem is.
Initially I do a fetch request to load some data, since here is working okey.
After with one value of this data I want to do another fetch. For that I call another function renderImage(), where I do the fetch and I render the code that I want.
My problem is that is not rendering nothing, because is not changing the status into loaded. And of course fetch is asynchronous and needs more time.
And I don't know how do it that it works and more simple because I thinks that I doing it a little bit complicated.
This is my code:
class Dashboard extends Component{
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
loaded: false,
datos: '',
}
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch(REQUEST_URL)
.then((response) => response.json())
.then ((responseData) =>{
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData),
})
})
}
renderLoadingView(){
return(
<View>
<Text>Cargando...</Text>
</View>
)
}
async renderImage(receta){
const REQUEST_URL = "yyyyyyyy" + receta.user_id;
const response = await fetch(REQUEST_URL);
const json = await response.json();
this.setState({ loaded : true});
return(
<Card >
<CardItem>
<Left>
<TouchableOpacity>
<Thumbnail style={{width: 50, height: 50, borderRadius: 25}} source={{uri: json.imageUrl}} />
</TouchableOpacity>
<Body>
<Text>{receta.Titulo}</Text>
<Text>{receta.Username}</Text>
</Body>
</Left>
</CardItem>
</Card>
)
}
renderReceta(receta){
return this.renderImage(receta);
}
render(){
if(!this.state.loaded){
return this.renderLoadingView();
}
else{
return(
<Container>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderReceta.bind(this)}
/>
</Container>
)
}
}
}

Perhaps not an "answer" but the question is a bit vague. This can be solved in many ways.
Option one:
Load the array first, then show the list, and async "sideload" each row's images.
Can be bad if you have a lot of images so watch out for that. Also you might load images here that you might never render (they are out of view, e.g. the user never scrolls to them), but you also wont load them twice to... pros and cons.
class Dashboard extends Component{
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
loaded: false,
datos: '',
images: {
}
}
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch(REQUEST_URL)
.then((response) => response.json())
.then ((responseData) =>{
this.setState({
loaded: true,
dataSource: this.state.dataSource.cloneWithRows(responseData),
});
this.fetchImageUrls(responseData);
});
}
fetchImageUrls(responseData){ //assuming respons data is an array of receta objects
responseData.forEach(({user_id})=>{
fetch("wwwsomewhere").then(r => r.json()).then(({imageUrl})=>{
this.setState({
images: Object.assign(this.state.images, {
[user_id]: imageUrl
})
});
});
});
}
renderLoadingView(){
return(
<View>
<Text>Cargando...</Text>
</View>
)
}
renderImage(receta){
const {Titulo, Username, user_id} = receta;
return(
<Card >
<CardItem>
<Left>
<TouchableOpacity>
{this.state.images[user_id] ?
<Thumbnail style={{width: 50, height: 50, borderRadius: 25}} source={{uri: this.state.images[user_id]}} />
: "Loading (load thumb here?)"
}
</TouchableOpacity>
<Body>
<Text>{receta.Titulo}</Text>
<Text>{receta.Username}</Text>
</Body>
</Left>
</CardItem>
</Card>
)
}
renderReceta(receta){
return this.renderImage(receta);
}
render(){
if(!this.state.loaded){
return this.renderLoadingView();
}
else{
return(
<Container>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderReceta.bind(this)}
/>
</Container>
)
}
}
}
Options 2:
Bundle upp all your loading into one and after resolve rerender.
class Dashboard extends Component{
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
loaded: false,
datos: '',
recetas: {
}
}
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch(REQUEST_URL)
.then((response) => response.json())
.then ((responseData) =>{
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData),
});
this.fetchImageUrls(responseData);
});
}
fetchImageUrls(responseData){ //assuming respons data is an array of receta objects
//Load all images
Promise.all(responseData.map(({user_id})=>{
return fetch("wwwsomewhere").then(r => r.json());
})).then((recetasArray)=>{
//When all thumb objects (Recetas) have been resolved
//map over the receta object array and create a hash (so you can access them by id later)
this.setState({
loaded: true,
recetas: recetasArray.reduce((acc, receta)=>{
acc[recept.user_id] = receta;
return acc;
},{})
});
});
}
renderLoadingView(){
return(
<View>
<Text>Cargando...</Text>
</View>
)
}
renderImage(receta){
const {Titulo, Username, user_id} = receta;
return(
<Card >
<CardItem>
<Left>
<TouchableOpacity>
<Thumbnail style={{width: 50, height: 50, borderRadius: 25}} source={{uri: this.state.recetas[user_id]}} />
</TouchableOpacity>
<Body>
<Text>{receta.Titulo}</Text>
<Text>{receta.Username}</Text>
</Body>
</Left>
</CardItem>
</Card>
)
}
renderReceta(receta){
return this.renderImage(receta);
}
render(){
if(!this.state.loaded){
return this.renderLoadingView();
}
else{
return(
<Container>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderReceta.bind(this)}
/>
</Container>
)
}
}
}
Untested code but just wanted to share my ideas.

Related

How to prevent re-rendering/fetching data in React Class Component?

I'm working in a music app using React Native, In the Home Screen I make a class component contains more than four FlatList and it's Get data from API "it's large data",
So i make a function For that, and put it inside componentDidMount(),
But I notice when I log the data after setState I see it twice Or more in RN-Debugger
So how can i prevent this happen?
because it's Affected in performance :)
here's a snippet of my code
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
url: '******',
loading: false,
minimal: false,
MiniURL: '',
songName: '',
currentTrackIndex: 0,
isPlaying: true,
};
}
getRecentSongs = async () => {
try {
let response = await API.get('/index');
let {recent_tracks} = response.data.data;
let recent_tunes = [];
recent_tracks.map(track =>
recent_tunes.push({
id: track.id,
name: track.name,
url: this.state.url + track.sounds,
img: this.state.url + track.avatar,
}),
);
let data = response.data.data;
this.setState({data, recent_tunes, loading: true}, () =>
console.log('data', this.state.data),
);
} catch (error) {
console.log(error);
this.setState({error: true});
}
};
componentDidMount() {
this.getRecentSongs();
}
_renderItem = ({item, index}) => {
const {url} = this.state;
return (
<TouchableNativeFeed
key={item.id}
onPress={() => {
this.props.navigation.navigate({
key: 'Player',
routeName: 'Player',
params: {
tunes: this.state.recent_tunes,
currentTrackIndex: index,
},
});
}}
background={TouchableNativeFeedback.Ripple('white')}
delayPressIn={0}
useForeground>
<Card style={styles.card} noShadow={true}>
<FastImage
style={{width: 200, height: 200}}
source={{uri: url + item.avatar}}
resizeMode={FastImage.resizeMode.cover}
style={styles.cardImg}
/>
<Body style={styles.cardItem}>
<View style={styles.radioCardName}>
<View style={styles.cardViewFlex}>
<Text style={styles.text}>{item.name}</Text>
</View>
</View>
</Body>
</Card>
</TouchableNativeFeed>
);
};
render(){
const {data} = this.state;
return(
...
{/* Recent Songs Here*/}
<View style={{marginVertical: 10}}>
<FlatList
horizontal={true}
showsHorizontalScrollIndicator={false}
data={data.recent_tracks}
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={<EmptyList />}
keyExtractor={(track, index) => track.id.toString()}
// initialNumToRender={10}
renderItem={this._renderItem}
/>
</View>
...
)
}
}
It's hard to tell from what's been posted, but is it possible that a key on one of the components is changing more often than you're expecting? React will trigger a full re-render if it detects any key changes.
ComponentDidMount will only be executed once and unmounted when it gets deleted. So that means that it is been created twice in some part of your application.
I have encountered a similar problem and it was regarding my navigation library, in my case, I was using react-navigation https://github.com/react-navigation/react-navigation/issues/2599.
So I can suggest checking if something has happened when your component is created and if it is doing it twice. Also, give a double check if your navigation is not doing the same.
Please use React.memo
It will not re-render the component without any relevent data in its props.
eg:
/**Your render item*/
const AddsItem = React.memo(({item, index}) => {
return (
<TouchableNativeFeed
...
...
</TouchableNativeFeed>
);
});
/**Your class*/
class Home extends React.Component {
constructor(props) {
...
}
render(){
const {data} = this.state;
return(
...
...
...
)
}

How to setState in different component React native?

My data from the api is loaded in a component and returns a view containing the values, which does show in the other component. But I want an activity indicator on that page to show until the data is completely loaded.
I've followed some tutorials about setState with parent-child, child-parent, sibling-sibling and parentless relations. But none seem to work the way I want it.
This is the screen that shows first. It starts with the 'refreshing' view, which will show an activityindicator until the state.refreshing is set to false.
export default class AgendaScreen extends React.Component {
static navigationOptions = {
title: "Agenda"
};
constructor(props) {
super(props);
this.state={
refreshing: true
}
}
render() {
if (this.state.refreshing){
return(
//loading view while data is loading
<ImageBackground source={ScreenBackground} style={styles.container}>
<View style={{flex:1, paddingTop:20}}>
<ActivityIndicator />
</View>
</ImageBackground>
)
}
return(
<ImageBackground source={ScreenBackground} style={styles.container}>
<ScrollView>
<ImageBackground style={[styles.header,{justifyContent:'flex-end'}]} source = {HeaderImage}>
<Text style={{color:'white', fontSize:12, alignSelf:'center', backgroundColor:'transparent', marginBottom:2}}>
Complete summary
</Text>
</ImageBackground>
<Text style={styles.text}>Agenda</Text>
<Dates />
</ScrollView>
</ImageBackground>
);
}
}
This is the dates component, which gives me the view that I call with in the previous code block.
export default class Dates extends React.Component {
constructor(props) {
super(props);
this.state = {
data:[],
fill:[],
refreshing:true,
dates:[],
list:[]
}
}
componentDidMount() {
this.getData();
}
getData() {
fetch("API")
.then((result)=>result.json())
.then((res=>{
this.setState({
data:res,
refreshing:false
});
this.setState({
fill:this.state.data[0]
});
this.getDates();
this.loop();
}))
.catch(error =>{
console.error(error);
});
};
onRefresh() {
//Clear old data
this.setState({
data:[]
});
//Function to call api for latest data
this.getData();
};
getDates() {
var str = t(this.state.test, 'content.rendered').safeString
var arr = str.split(',');
var dates = [];
arr.forEach(function(e) {
dates.push(e.match(/;(\d{0,2}.[a-zA-Z]+)/g)[0].replace(';',''));
});
this.setState({
dates: dates
})
};
tempList=[];
loop(){
for (x=0;x<this.state.data.length;x++)
{
var fill=this.state.data[x]
this.tempList.push(
<View style={{flex:1}}>
<FlatList
data={[fill]}
renderItem={({item})=>
<View>
<Text style={styles.text}>
{t(item, 'title.rendered').safeObject}
</Text>
</View>
}
refreshControl={
<RefreshControl
//refresh control used for pull to refresh
refreshing={this.state.refreshing}
onRefresh={this.onRefresh.bind(this)}
/>
}
keyExtractor={(item, index) => index.toString()}
/></View>
)
}
this.setState({
list:this.tempList
})
}
render() {
return(
<View style={{flex:1, borderWidth:10}}><Text></Text>{this.state.list}
</View>
);
}
}
What I need is when Dates succesfully loaded his data from the api and returns the view, that the AgendaScreen state.refreshing will be stated to false.
Add below to your AgendaScreen Component
this.refresHandler = (e) =>{
this.setState({
refreshing:e
})
}
Add below props inside <Dates />
<Dates refresHandler={this.refresHandler}/>
change below code in Dates Component
getData() {
fetch("API")
.then((result)=>result.json())
.then((res=>{
this.setState({
data:res,
refreshing:false
});
this.setState({
fill:this.state.data[0]
});
this.getDates();
this.loop();
}))
.then(() =>{
this.props.refresHandler(this.state.refreshing)
})
.catch(error =>{
console.error(error);
});
}

How to Fetch APIs and Print first element in response Array?

My React Native app fetch API data and I need to print the first index of response but it's not, and gets all of the "ozone" for example in all child of the parent Array and when I print val[0] when Mapping I have nothing printed
My Code|
export default class App extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, dataSource: null };
}
async componentDidMount() {
let API_WEATHER =
"https://api.weatherbit.io/v2.0/forecast/daily?city=Raleigh,NC&key={API_KEY}";
fetch(API_WEATHER)
.then(response => response.json())
.then(responseJson => {
console.log(responseJson.data);
this.setState({
isLoading: false,
dataSource: responseJson.data
});
})
.catch(error => {
console.log(error);
});
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator size="large" />
</View>
);
}
let weather= this.state.dataSource.map((val, key) => {
return (
<Text key={key}>
{val.ozone}
</Text>
);
});
return (
<ScrollView style={styles.container}>
<ScrollView>
<View>
<Text>{weather}</Text>
</View>
</ScrollView>
</ScrollView>
);
}
In this part of the code when i log the respone JSON obj
.then(responseJson => {
console.log(responseJson.data);
console.log(responseJson.data[0]);
console.log(responseJson.data[0].datetime);
}
i have what i need, but when print them in View i have Erroe
look at the Images
You're probably the first key of the object.
obj[Object.keys(obj)[0]];
Also, you can use
Try the for … in loop and break after the first iteration
for (var prop in object) {
// object[prop]
break;
}

React Native function bind parameters

I am trying to render a list which calls a function to render the rows, but how can I pass the paramater data to the function/handler?
The data is in json style.
I tried many things but I always get an error back, what am I doing wrong?
This the code:
componentDidMount() {
fetch('http://link/json.php', {
method: 'GET'
})
.then((response) => response.json())
.then((responseJson) => {
dataJson = responseJson;
this.setState({
preLoad: true
});
})
.catch((error) => {
});
}
renderRows(item){
if(item.type == 2){
return(
<ListItem>
<Thumbnail square size={80} source={{ uri: 'item.image' }} />
<Body>
<Text>{item.title}</Text>
<Text note>{item.desc}</Text>
</Body>
</ListItem>
)
}
}
renderMenu(){
if(this.state.preLoad){
return(
<List dataArray={dataJson}
renderRow={(item) =>
this.renderRows().bind(this,item)
}>
</List>
)
}else{
return(
<Body>{shimmerRows}</Body>
)
}
}
render() {
return (
<Container style={styles.container}>
{this.renderMenu()}
</Container>
);
}
It seems you just need to save your response to the state.
this.setState({
preLoad: true,
dataJson: responseJson,
});
And do the binding correctly.
<List dataArray={this.state.dataJson}
renderRow={(item) => this.renderRows.bind(this,item)}>
</List>

how to show a component depending of picker value selected? [React native]

I'm trying to make a game with react native and I want to show a different options when i change the picker value.
basically when I select the first option on the picker a component has to appear and when I select the second one another component.
I tried this function but not working
pickerOptionText = () => {
if (this.state.PickerValueHolder==this.state.filter[0]) {
return (
<Text>{instructions[2]}</Text>
);
}else {
return (
<Text>{instructions[1]}</Text>
);
}
return null;
}
here is my code
export default class Facil extends Component {
constructor(props)
{
super(props);
this.state = {
isLoading: true,
PickerValueHolder : '',
filter: [
{
"option":"Palabras por categoria"
},
{
"option":"Palabras por caracteres"
}
],
dataSource:[]
}
}
componentDidMount() {
return fetch(API_URL)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
})
})
.catch((error) => {
console.error(error);
});
}
render() {
const resizeMode = 'stretch';
pickerOptionText = () => {
if (this.state.PickerValueHolder==this.state.filter[0]) {
return (
<Text>{instructions[2]}</Text>
);
}else {
return (
<Text>{instructions[1]}</Text>
);
}
return null;
}
return (
<View style={styles.container}>
<Image source={require('../../Images/HomeLayout.png')}
style={styles.imagen}
/>
<View style={styles.mView}>
<View style={styles.panel}>
<Text style={styles.titlePanel}>MODO FACIL</Text>
<Text style={styles.instructions}>{instructions[0]}</Text>
<View style={styles.picker}>
<Picker
selectedValue={this.state.PickerValueHolder}
style={ {height: '100%',width: '100%'}}
mode="dropdown"
onValueChange={(itemValue, itemIndex) => this.setState({PickerValueHolder: itemValue})} >
{ this.state.filter.map((item, key)=>(
<Picker.Item label={item.option} value={item.option} key={key} />)
)}
</Picker>
</View>
<View style={styles.gameOpt}>
<Text>[dynamic options]</Text>
{pickerOptionText}
</View>
</View>
</View>
<TouchableOpacity style={styles.button}><Text style={styles.btnText}>Play!</Text></TouchableOpacity>
</View>
);
}
}
You forgot '()'.
pickerOptionText is a function, not a React component.
<Text>[dynamic options]</Text>
{pickerOptionText}
to:
<Text>[dynamic options]</Text>
{pickerOptionText()}
You can try using Conditional Rendering of JSX, by this you can use ternary operator and a simple if condition. this is written as:
{this.state.PickerValueHolder==this.state.filter[0] ?
<Text>{instructions[2]}</Text>
:<Text>{instructions[1]}</Text>
}
and if you need simple if condition then,
{ condition == true && <Text>your text here</Text>
}

Categories

Resources