React Native useState Render Error : [Too many re-renders.] - react-native

This is my code:
export default function App() {
const [onProcess, setOnProcess] = useState("normal")
var myid = "123"
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.npoint.io/0294bea2185268c9ac70')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log('ERR :', error))
},[]);
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation
setOnProcess(set)
console.log(data[x].situation)
break
}
}
const rt_normal = (
<View style={styles.container}>
<Text> This is normal view </Text>
</View>
)
const rt_process = (
<View style={styles.container}>
<Text> This is process view </Text>
</View>
)
if (onProcess == "normal") {
return rt_normal
}
else if (onProcess == "_on_process") {
return rt_process
}
}
The error I got is:
:[Render Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.]
This happens because of setOnProcess(set) code. How can I solve this?

You should remove your for...in loop and refactor to utilise useEffect.
useEffect(() => {
// Get a specific entry where client_id matches myId.
const filteredItem = data.find(item => item.client_id === myId);
// Perform a check as .find() can return undefined.
if(filteredItem.situation) {
setSituation(filteredItem.situation);
}
}, [data]);

Put the for loop inside a useEffect
(Untested) example:
useEffect(() => {
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation;
setOnProcess(set);
console.log(data[x].situation);
break;
}
}
}, [data]);

Related

Component array not shown in React Native?

I'm new to React Native, and I wonder why my code isn't working.
I know thorugh console.logging that my output array is full with the correct data, but for some reason in the return when I try to write out the output it doesnt seem to writing anything to the mobile screen. I wonder why that might be.
const ChampionScreen = () => {
const [champions, setChampions] = useState([]);
var output = [];
useEffect(() => {
AxiosService.getChampions()
.then(data => {
setChampions(data);
var champarr =[];
Object.keys(champions).forEach(function(key){
champarr.push(champions[key]);
})
for(let i = 0; i < champarr.length;i++){
let champion = JSON.parse(JSON.stringify(champarr[i]));
var tempItem = (
<View key={i}>
<Text>{champion.name}</Text>
</View>
);
output[i] = (tempItem);
}
}).catch(err => console.error(err))
},[])
return (
<ScrollView>
<View>
{output}
</View>
</ScrollView>
)
}
you did not define output as a state variable and it will not trigger a render that way. after that, when you are mapping the data object with Object.keys method, you are kind of going with luck because the state updates are asyncronous, so you should not set the state and immediately use the state, use the data you already fetched. here it is:
const ChampionScreen = () => {
const [champions, setChampions] = useState([]);
const [output, setOutput] = useState([]);
useEffect(() => {
AxiosService.getChampions()
.then(data => {
setChampions(data);
var champarr = [];
let axiosOutput = [];
Object.keys(data).forEach(function(key){
champarr.push(data[key]);
})
for(let i = 0; i < champarr.length;i++){
let champion = JSON.parse(JSON.stringify(champarr[i]));
var tempItem = (
<View key={i}>
<Text>{champion.name}</Text>
</View>
);
axiosOutput.push(tempItem);
}
setOutput(axiosOutput)
}).catch(err => console.error(err))
},[])
return (
<ScrollView>
<View>
{output}
</View>
</ScrollView>
)
}

Display all posts from database

I have a Firestore collection, schemed as follows:
posts{
uid{
userPosts{
postID{
creation:
postText:
}
}
}
}
I want to display all of the posts, so I've made the corresponding queries and saved them in posts - an array of all the posts that I later iterate through.
The problem with the way I do it is that it keeps adding the same posts every render. So I've tried to set the array each time, but that way the code never passes through these posts && posts.length > 0 condition.
I'm really new to RN and JS in general, but what I was expecting is
Nothing to show here
at first, and then the list of posts.
The complete component:
import { Text, Pressable, FlatList, SafeAreaView } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import { db } from "../../../firebase";
import Post from "../../API/Post";
import { collection, getDocs } from "firebase/firestore";
const FeedScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPostData = async () => {
setPosts([]); // ---> Without this line the posts keeps adding each render
const q = collection(db, "posts");
const docSnap = await getDocs(q);
docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
tmpSnap.docs.map(async (element) => {
setPosts((prev) => {
prev.push(element.data());
return prev;
});
});
});
};
getPostData().catch(console.error);
return;
}, []);
return (
<SafeAreaView style={globalStyles.global}>
{posts && posts.length > 0 ? (
<FlatList
data={posts}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
keyExtractor={(item, index) => index.toString()}
/>
) : (
<Text>Nothing to show here</Text>
)}
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreatePost", { navigation });
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</SafeAreaView>
);
};
export default FeedScreen;
As said, I'm new to this so I'd love an explanation of what actually happens and how to do it properly.
I think the prev value of setPosts will always be [] since it does not immediately update if you call it. A standard way to do it is to call setPosts at the end of your function. Can you try this one?
useEffect(() => {
const getPostData = async () => {
const q = collection(db, "posts");
const docSnap = await getDocs(q);
const promises = docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
return tmpSnap.docs.map((element) => element.data());
});
const arrayOfPosts = await Promise.all(promises);
let newPosts = [];
arrayOfPosts.forEach((posts) => {
newPosts = [...newPosts, ...posts];
});
setPosts(newPosts);
};
getPostData().catch(console.error);
return;
}, []);

remove duplicate error from JSON value React Native

I am trying to pass filtered value from JSON to the parent component, however I've tried using Set but seems the output is still the same. The component that I'm using to render the JSON is picker from native-base. I want to filter out the repeated value in my picker. Greatly appreciated if anyone can help me.
enter image description here
Here's my code.
Picker.js
const DefaultPicker = ({labelItem, pickerWidth, onHandleValue, ...rest}) => {
const context = useContext(WindowContext);
const [selectedValue, setSelectedValue] = useState('-Select-');
const [data, setData] = useState([]);
const {user, setUser} = useContext(AuthContext);
function onNewData() {
if (user) {
user.getIdToken().then((idToken) => {
Axios.get('URL_ENDPOINT', {
headers: {
Authorization: 'Bearer' + idToken,
},
})
.then(({data}) => {
setData(data.features);
// console.log(data.features);
})
.catch((error) => {
console.error(error);
});
});
}
}
useEffect(() => {
const form = onNewData(onNewData);
return form;
}, []);
return (
<PickerWrapper>
<PickerItem
width={pickerWidth}
height="60"
mode="dropdown"
selectedValue={selectedValue}
onValueChange={(itemValue, itemIndex) => {
setSelectedValue({itemValue});
}}>
{Array.from(
new Set(
data.map((value, index) => (
<PickerItem.Item
key={index}
label={value.properties[labelItem]}
value={value.properties[labelItem]}
{...rest}
/>
)),
),
)}
</PickerItem>
</PickerWrapper>
);
};
And here is my parent component
SiteData.js
const SiteData = () => {
const [values, setValues] = useState([]);
const onHandleValue = (params) => {
setValues(params);
console.log(params);
};
return (
<ScrollableView>
<DetailContainer>
<DetailWrapper>
<DetailTitle>Site Data</DetailTitle>
<DetailSubtitle marginTop="10">
Insert new data found during your audit or observation session
</DetailSubtitle>
<DetailSubcontainer>
<DefaultPicker
labelItem={'category'} <-- receive value from child
pickerWidth="100%"
onHandleValue={onHandleValue}
/>
</DetailSubcontainer>
</DetailWrapper>
</DetailContainer>
</ScrollableView>
);
};
UPDATE 1:
I'm using the filter() method so i can create a new array but it returns only one value in the picker list.
const indexData = data.filter(
({category}, index) => {
return (
data.findIndex(
(item) =>
item.category === category,
) === index
);
},
);
The output
enter image description here
I fixed my code by adding this on child component
var setObj = new Set();
var result = data.reduce((acc,item)=>{
if(!setObj.has(item.category)){
setObj.add(item.category,item)
acc.push(item)
}
return acc;
},[]);

How to call a variable with data in React Native

Sometghing really basic but I didn't understant.
Once I get the contacts how can I use them to populate the Flatlist?
I always get Can't find variable: contacts
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
const contact = data[0];
console.log(contact);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={contact}
keyExtractor={contact.id}
renderItem={({ item }) => (
<ContactItem
firstName={item.firstName}
/>
</View>
);
};
export default ContactsScreen;
I think it's really simple, I just don't understand
You need to keep your contacts in the component's state. So every time you change your state, your component will render itself and you will see the updated data.
Change your code with the following. Don't forget to import useState.
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
const [myContacts, setMyContacts] = useState([]);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
setMyContacts(data);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={myContacts}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Text>{item.firstName}</Text>
)}
/>
</View>
);
};
export default ContactsScreen;
Answer from my comment:
I think that might be because of the scope of the variable , it could be that RN doenst know it exists because it only lives inside the function. I guess you could set up a State and then assign the values from contact to the state and in ur flatlist call data ={ this.state.contact}.
or by using hooks like you do :
if (data.length > 0) {
setContact(data);
}
and call it in flatlist:
data={myContact} // if named so in state declaration

How to make react-native-banner-carousel load images from API(asynchronous)

I'm using https://github.com/f111fei/react-native-banner-carousel/
It works fined with hardcoded images path.
But this error happened if my images array is empty. It will show error as this image
I guess it caused by empty array (please correct me if im wrong). The state.carousels yet to loading to state when it render.
How can I make it asynchronous, so it can load the images dynamically.
So this is my code.
Dashboard.js
componentWillMount(){
this.props.carouselFetch();
}
renderPage(image, index) {
return (
<View key={index}>
<ImageFluid
source={{ uri: image }}
originalWidth={ 2500 }
originalHeight= { 1000 }
/>
</View>
);
}
render(){
const images = this.props.carousels;
return(
......
<Carousel
autoplay
autoplayTimeout={5000}
loop
index={0}
showsPageIndicator={ false }
pageSize={BannerWidth}
>
{ images.map((image, index) => this.renderPage(image, index))}
</Carousel>
......
);
}
const mapStateToProps = (state) => {
const carousels = state.carousel;
return { carousels };
};
CarouselActions.js
export const carouselFetch = () => {
return (dispatch) => {
fetch('API json')
.then((response) => response.json())
.then((response) => {
if (response.Status === 'Fail') {
return Promise.reject(response)
}
return response
})
.then(carousels => {
carouselFetchSuccess(dispatch, carousels);
})
.catch(() => console.log("Error"));
};
};
const carouselFetchSuccess = (dispatch, carousels) => {
dispatch({
type: CAROUSEL_FETCH_SUCCESS,
payload: _.map(carousels.data, i => i.image_path)
});
};
My Sample API json
The package required sample array method
render(){
const images = this.props.carousels;
if (!images || images.length === 0) {
return null;
}
return(
......
<Carousel
autoplay
autoplayTimeout={5000}
loop
index={0}
showsPageIndicator={ false }
pageSize={BannerWidth}
>
{ images.map((image, index) => this.renderPage(image, index))}
</Carousel>
......
);
}
Don't render carousel when the image list length is 0.
First use :
const images = ( this.props.carousels || [] ).map( (image) => ( {
value: carousels .name,
label: carousels .id,
order: carousels .data.
} );
You have a simple array of objects not an nested array, your response it's a simple res.data and not a res.carousel.data, if you use console.log(res) you will see your array, check that.