Refresh Container/Screen with useState AND map() in React Native - react-native

in my application, after receiving the updated data from the API I use useState, but this does not reflect on the information on the screen, I need to goBack and forward to change the information.
When I click on the button I save the information and on the return I need to update that the task has already been done, showing an "OK", but this is not updated, even though the "schedules" variable is right.
Where am I going wrong? What do I need to do to "return" and run again?
Thanks a lot!
import React, { useState, useEffect, useCallback } from "react";
...
const Pdvs = () => {
const [scheduletasks, setScheduletasks] = useState([]);
...
onSave = async (id) => {.....
const responseTask = await api.post("/schedules/fulldetails",{id});
setScheduletasks(responseTask.data);
...
return (
<Container>
{scheduletasks.map((keys) => (
{keys.done ? "OK"
) : ""}
<Button title="Done"
onPress={() =>
handleSave(keys.id)
}
/>

Your codes a little broken, especially in return(). Please revise it to get better help.

Sorry, I put just a piece of the code, to filter where is not the problem. Below more compleat code:
My "onSubmit" should be where you update the variable for useState
The big problem is in the Return, variable {keys.done}, is just that I want! :)
Thanks a lot
onSubmit = async (inputText) => {
try {
const { schedule_id, product_id, task_id } = scheduletasksResult;
const gpsPosition = await getLocationAsync();
const response = await api.post(`/scheduletasks/add`, {
schedule_id,
product_id,
task_id,
result: inputText,
gpsPosition,
});
if (response.status === 200) {
setScheduletasksID(response.data.id);
} else {
setScheduletasksID(0);
}
return response.data.id;
} catch (error) {
console.log(error.response.data);
}
};
useEffect(() => {
async function loadPdvs() {
setCompany_id(company_id[1]);
const response = await api.post("/schedules", {
company_id: company_id[1],
id,
});
setSchedules(response.data[0]);
const responseTask = await api.post("/schedules/fulldetails", {
company_id: company_id[1],
id,
});
setScheduletasks(responseTask.data);
}
loadPdvs();
}, []);
return (
<Container>
<RouteTitle>{schedules.tradeName}</RouteTitle>
<ScrollView>
{scheduletasks.map((keys) => (
<RoutesContainer key={keys.task_id}>
<RouteDetail>
{keys.task_name}
{keys.done ? (
<FontAwesomeIcon
icon={faCheckCircle}
/>
) : null}
</RouteDetail>
{keys.textRequired ? (
<Button
title="Observações"
onPress={() =>
showDialog(keys.schedule_id, keys.product_id, keys.task_id)
}
/>
) : null}
</RoutesContainer>
))}
</ScrollView>
</Container>
);

Related

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;
}, []);

(React Native && RTK Query) How to make sure the data has been returned when use conditional fetching

export function Login() {
const [skip, setSkip] = useState(true);
const { data, isFetching } = useVerifyUserQuery(userState, {
skip,
});
const LoginButton = () => (
<Button
title="Login"
onPress={() => {
setSkip((prev) => !prev);
}}
/>
);
return (
…
)
}
The requirement is to make a request when the button is pressed, and then store the returned data in a constant. Is there a good way to make sure data is returned before I store it.
Here is one of my solutions. Obviously it may cause some problems.
onPress={() => {
setSkip((prev) => !prev);
while(isFetching){}
// save data
}}
And with the code below, storeData will be called multiple times.
export function Login() {
const [skip, setSkip] = useState(true);
const { data, isFetching } = useVerifyUserQuery(userState, {
skip,
});
if (!isFetching && IsNotEmpty(data)){
storeData();
}
const LoginButton = () => (
<Button
title="Login"
onPress={() => {
setSkip((prev) => !prev);
}}
/>
);
return (
…
)
}
It looks like you just want to use the lazy version - useLazyVerifyUserQuery instead of common. It will be like:
export function Login() {
const [ verifyUser ] = useLazyVerifyUserQuery();
const handleLogin = async () => {
const data = await verifyUser(userState).unwrap();
// Probably you would want to use `storeData` somehow here?
}
const LoginButton = () => (
<Button
title="Login"
onPress={handleLogin}
/>
);
return (
...
)
}
PS: just a warning - using a nested component definition, like LoginButton inside Login - is a known antipattern that may cause significant performance issues.

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

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]);

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 update FlatList if data is updated

The way I get the data is inside the componentWillMount method, its working fine when it renders the FlatList when the app boots up, but when I'm adding data, all I want is to update also my flatList. How can I do that here's the code snippet:
componentWillMount() {
console.log(`componentDidUpdate`, prevProps);
const user = firebase.auth().currentUser;
if (user != null) {
const db = firebase.firestore();
const docRef = db.collection('userCheckInHistory').doc(user.uid);
docRef
.get()
.then(doc => {
if (doc.exists) {
let shopId = this.props.shopId;
let userShopHistoryData = doc.data();
let userShopPick = userShopHistoryData[shopId];
this.setState({
checkInHistory: userShopPick,
shopId: this.props.shopId
});
} else {
console.log('No such document!');
return false;
}
})
.catch(function(error) {
console.log('Error getting document:', error);
});
}
}
Now this is my FlatList code:
renderHistoryList() {
const sortedData = Common.getArrayByObject(this.state.checkInHistory);
if (this.state.checkInHistory !== '') {
return (
<FlatList
data={sortedData}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={({ item }) => (
<MyListItem
id={item}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item)}
title={item}
/>
)}
/>
);
} else {
return <Spinner />;
}
}
When you get your data from api set your state with that data. And then if you add data and setState, react native will rerender app and you will see your data on the list.