How to render item when data is updated react native - reactjs

Hi guys I am currently facing a problem where in I need to update the data inside of a pop up
here's my code
useEffect(() => {
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
accepted,id,name,rating,services,userDestinationLat,userDestinationLng,userOriginLat,userOriginLng
} = doc.data();
list.push({ accepted, id, name, rating, services, userDestinationLat, userDestinationLng, userOriginLat, userOriginLng });
});
setUserBookingData(list);
});
}, []);
useEffect(() => {
// use this just to get userbookingData
console.log(userBookingData);
},[userBookingData]);
const [newOrder, setNewOrder] = useState({
id: '1',
service: userBookingData.services,
originLatitude : originalPos.latitude,
originLongitude: originalPos.longitude,
destinationLatitude: 45.24953,
destinationLongitude: -76.360733,
user:{
rating: userBookingData.rating,
name: userBookingData.name,
}
});
now here on my return view
return(
{ !userBookingData.length ? (<NewOrderPopUp
newOrder={newOrder}
onDecline={onDecline}
duration={2}
distance={0.5}
onAccept={() => onAccept(newOrder)}
/>) : (
<View></View>
)
}
);
here's the popup component it's in another page
const NewOrderPopUp = ({newOrder, onAccept, onDecline, duration, distance}) => {
return (
<View style={styles.root}>
<Pressable onPress={onDecline} style={styles.declineButton}>
<Text style={styles.declineText}>Decline</Text>
</Pressable>
<Pressable onPress={onAccept} style={styles.popupContainer}>
<View style = {styles.row}>
<Text style = {styles.service}>{newOrder.service}</Text>
{/* <Image source={{}}/> */}
<View style={styles.userBg}>
<FontAwesome name={"user"} color={"white"} size={35}/>
</View>
<Text style = {styles.service}>
<AntDesign name={"star"} size={16}/>
{newOrder.user.rating}
</Text>
</View>
<Text style = {styles.minutes}>{duration} mins</Text>
<Text style = {styles.distance}>{distance} KM</Text>
</Pressable>
</View>
);
};
it won't update the data / show the pop up wen there's a data
The expectation output is it should be able to pop up the NewOrderPopUp screen when there's a data.
here's the data from the userBookingData
I need to display it here

The fact that you are filling userBookingData in first useEffect does not allow you to write an useState getting value from userBookingData itself.
React doesn't work like that.
Not only but if I understand correctly, userBookingData is an array so write userBookingData.services returns null.
Lets suppose that you want to have in newOrder the first element that comes from userBookingData. In this case, you should write something like:
const [newOrder, setNewOrder] = useState({});
useEffect(() => {
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
accepted,id,name,rating,services,userDestinationLat,userDestinationLng,userOriginLat,userOriginLng
} = doc.data();
list.push({ accepted, id, name, rating, services, userDestinationLat, userDestinationLng, userOriginLat, userOriginLng });
});
setUserBookingData(list);
// here set newOrder
let resultObj = {};
resultObj.id = 1;
resultObj.service = list[0].services;
...
setNewOrder(resultObj);
});
}, []);

Related

How do I access the data from a referenced firestore document?

My data in firestore is structured as shown in the pictures. In my code, I am using a map of the exercises array within my workout document like this:
{workout.exercises.map((exercise) => {
return (
<Text>Want exercise name here</Text>
)
I believe I have to use doc.data() from getDoc(exercise).then((doc) => to get the actual values, but I can't seem to figure out how to actually render the exercise name. It's driving me crazy, any help would be greatly appreciated!
This is the exercise object referenced in the exercises array from the picture above:
EDIT: If the exercises array only has one element, it works properly, but if it has >1, the data seems to keep rendering infinitely which results in flashing and changing text.
EDIT2: Here's my full component code in case that helps:
export function WorkoutDisplayScreen({ navigation }) {
const [workouts, setWorkouts] = React.useState([]);
const workoutsArray = [];
const [isFirstLoad, setIsFirstLoad] = useState(true);
const [exerciseData, setExerciseData] = useState({});
async function getData(exercise) {
await getDoc(exercise).then((doc) => {
setExerciseData(doc.data());
});
}
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsDocs = await getDocs(workoutsCollection);
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};
useEffect(() => {
console.log('Rendering...');
if (isFirstLoad) {
getWorkouts();
}
setIsFirstLoad(false);
}, []);
return (
<ScreenContainer>
<View style={styles.headerrow}>
<TouchableOpacity
onPress={() => {
navigation.goBack();
}}
>
<AntDesign name="leftcircle" size={24} color="black" style={styles.headericon} />
</TouchableOpacity>
<Text style={styles.header}>My Workouts</Text>
</View>
{workouts &&
workouts.map((workout) => {
return (
<View key={workout.id}>
<View style={styles.workoutnamecontainer}>
<Text style={styles.workoutnametext}>{workout.name}</Text>
</View>
{workout.exercises.map((exercise, index) => {
getData(exercise);
return (
<View style={styles.exercisecontainer} key={index}>
<Text style={styles.exercisetext}>{exerciseData.name}</Text>
<Text style={styles.exercisetext}>{exerciseData.type}</Text>
<Text style={styles.exercisetext}>{exerciseData.weight}</Text>
<Text style={styles.exercisetext}>{exerciseData.reps}</Text>
</View>
);
})}
</View>
);
})}
</ScreenContainer>
);
}
EDIT3:
I can now log the data in the correct format with, but trying to render it on screen is still not working correctly.
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
let workoutsDocs = workoutsSnapshot.docs;
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
workouts.map((workout) => {
workout.exercises.forEach((exercise) => {
getDoc(exercise).then((doc) => {
console.log(workout.name + ':');
console.log(doc.data());
});
});
});
};
I believe you should do all the data-fetching i.e. for all the workouts and every exercise in each workout in the useEffect itself. I have never seen code where data fetching is done inside the JSX part of the function. I am highly skeptical about it.
Also, one thing I noticed while looking through your code is that you are iterating over the snapshot instead of docs in the the snapshot inside your getWorkouts() function. It should be as follows:
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
workoutsDocs = workoutsSnapshot.docs; // This line must be added to your code
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};

React Native: Why I can not pass a reference to another screen?

I have 3 screens and I can navigate between them. The first one is "Reiew Screen" where the user can put a raiting, write a title and description of their review. The second one (most problematic for me) is the media screen where the user can select multiple photos to support their review. The third screen is "Review screen" where the user can again double check and edit their review (if they need to).
On the review screen I see the photos the user has selected and if I want I can remove some of them. If I want, I also can edit the title and the description of the review.
Editing the photos and keeping the edit consistent with the "Media Screen" is done using refs. I am using ImageBrowser from here so that I can keep consistent state between MediaScreen and ReviewScreen when the user deletes the photos. The problem is when I want to edit the title and the description. If I want to edit title.description I need to naigate from Reiew page to Rate page (because Rate page contains the title and the description). When I press "done" I need to navigate back to Review page where I see the updated title and the updated description as well as the photos which I have selected on the previous steps. When I navigate from Rate page to Review page I have error that the reference of ImageBrowser I have created is null. I tried passing this reference between the screens, as a property but for some reason it seems I am passing undefined.
I am thinking maybe the refs object which I have created have life span and it is expired?
I am posting the pages and how I navigate between them:
Rate Screen
const RateScreen = (props) => {
const { place } = props.route.params;
//imageBrowser is the object which I try to retrieve from the Review page, but it comes as undefined
const { finalObject, imageBrowser } = props.route.params;
console.log("RATE SCREEN IS BEING RERENDERED: ", props.route.params);
const [rating, setRating] = useState(0);
const [title, setTitle] = useState(null);
const [description, setDescription] = useState(null);
// again trying to retrieve the refs
const [gallery, setGallery] = useState(props.route.params.imageBrowser);
function ratingCompleted(value) {
// not relevant
}
function updateTitle(value) {
//not relevant
}
function updateDescription(value) {
//not relevant
}
useEffect(() => {
// way to fetch some value for imageBrowser but it is still null
console.log("useeffect for image browser is being rendered");
if (imageBrowser) {
setGallery(imageBrowser);
}
}, [imageBrowser]);
function navigate() {
if (finalObject) {
//in the case I have edited the object I navigate back to Review Screen
props.navigation.navigate("Review Screen", {
finalObject: {
...finalObject,
rating: rating,
title: title,
description: description,
},
// it comes as null so my code throws an exception
imageBrowser: gallery,
});
} else {
// In the case I have just created a review I am just navigating to the next step which is Add Media screen
props.navigation.navigate("Add Media", {
rateObject: { ...rateObject, place: place },
});
}
}
useLayoutEffect(() => {
props.navigation.setOptions({
headerRight: () => navigate()
),
});
}, [rating, title, description]);
return (
<View>
<TextInput
value={title}
onChangeText={(value) => updateTitle(value)}
/>
<TextInput
value={description}
onChangeText={(value) => updateDescription(value)}
/>
</View>
);
};
export default RateScreen;
Part of my Add Media Screen where I create the refs, and I pass it to the Review Page:
const GalleryScreen = (props) => {
const { selected, setPhotos, setImageBrowser } = props.route.params;
const [newSelected, setNewSelected] = useState(selected);
//using refs
const imageBrowserRef = useRef(null);
useEffect(() => {
setNewSelected(selected);
}, []);
useEffect(() => {
if (imageBrowserRef) {
setImageBrowser(imageBrowserRef);
}
}, [imageBrowserRef]);
const renderSelectedComponent = (number) => {
return (
//not related
);
};
return (
<View style={styles.viewStyle}>
<ImageBrowser
// creating the ref
ref={imageBrowserRef}
max={10}
callback={(promise) => {
//not related
}}
renderSelectedComponent={renderSelectedComponent}
/>
</View>
);
};
useLayoutEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() => {
props.navigation.navigate("Review Screen", {
finalObject: {
...rateObject,
photos: photos,
},
// passing the refs, which for this step works fine
imageBrowser: imageBrowserRef,
});
}}
>
<AntDesign name="check" size={24} color="white" />
</TouchableOpacity>
</View>
),
});
Finally my "Review Page":
import {
renderTitle,
renderRating,
renderDescription,
renderMedia,
} from "./renderItems";
const ReviewScreen = (props) => {
//at this step imageBrowser is not null and my interactions with the browser are as expected
const { finalObject, imageBrowser } = props.route.params;
const { navigation } = props;
const [editedObject, setEditedObject] = useState(finalObject);
const [rating, setRating] = useState(finalObject.rating);
function ratingCompleted(value) {
//not relevant
}
function removePhoto(item) {
//again as I expect, the removed photo is removed from BOTH screens (Media screen and Review screen)
let index = imageBrowser.current.state.photos.indexOf(item);
let updatedPhotos = editedObject.photos.filter(function (photo) {
return photo != item;
});
imageBrowser.current.selectImage(index);
setEditedObject({
...editedObject,
photos: updatedPhotos,
});
}
useLayoutEffect(() => {
//doesnt matter
),
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
// if I go back to Add Media screen, thanks to refs both screens are consistent
navigation.navigate("Add Media", {
rateObject: editedObject,
});
}}
/>
),
});
}, [editedObject]);
return (
<View>
{renderRating(editedObject, ratingCompleted)}
// with render title I am trying to pass imageBrowser refs as a property but it doesnt work
{renderTitle(
{ ...editedObject, rating: rating },
imageBrowser,
props.navigation
)}
//similar with render description
{renderDescription(
{ ...editedObject, rating: rating },
imageBrowser,
props.navigation
)}
//render media does not navigate to anywhere, it simply displays the photos
{renderMedia(editedObject, removePhoto)}
</View>
);
};
My renderTitle mthod:
export const renderTitle = (finalObject, imageBrowser, navigation) => {
return (
<SafeAreaView style={{ flexDirection: "row", height: 60 }}>
<ScrollView style={{ marginHorizontal: 10, marginTop: 10 }}>
<Text style={styles.titleStyle}>{finalObject.title}</Text>
</ScrollView>
<TouchableOpacity
onPress={() => {
make sure imageBrowser is not null and it is not
console.log("trying to edit title: ", imageBrowser.current.props);
//again on this page I see that imageBrowser is not null, but passing it to the "Rate page" somehow magically it is null
navigation.navigate("Rate Your Visit", {
finalObject: finalObject,
imageBrowser: imageBrowser,
});
}}
>
<MaterialIcons name="edit" size={24} color="#0696d4" />
</TouchableOpacity>
</SafeAreaView>
);
};
Any ideas are welcome

Change color of a component inside a function dinamically

I have the next component:
const DetailsInformation = () => {
const [details, setDetails] = useState(null)
useEffect(()=>{
getDetails()
}, [])
const getDetails = async () => {
await getData().then((details)=>{
details.forEach((detail)=>{
detail.posibles_valores.forEach((valor)=>{
valor.selected = false
})
})
setDetails(filteredDetails)
})
}
const getIconColor = (value) => {
if(!value) return 'grey';
return Colors.primary
}
const updateDetail = (nombre_detalle, detalle) => {
let detailToUpdateIndex = details.findIndex(det => det.nombre.toLowerCase() == detalle.toLowerCase())
let innerDetailToUpdateIndex = details[detailToUpdateIndex].posibles_valores.findIndex(valor => valor.valor_detalle.toLowerCase() == nombre_detalle.toLowerCase())
let updatedDetails = details;
updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected = !updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected
updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].color = getIconColor(!updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected)
setDetails(updatedDetails)
}
const generateDetailValues = (detail) => {
let detailSelected = details.find(det => det.nombre.toLowerCase() == detail.toLowerCase())
return (<View>
{detailSelected.posibles_valores.map((detalle, index)=>(
<TouchableWithoutFeedback key={detalle.valor_detalle} onPress={()=>{updateDetail(detalle.valor_detalle, detail, index)}}>
<View style={{justifyContent:'space-between', marginHorizontal:30, alignItems:'center', flex:1, flexDirection:'row'}}>
<Text bold style={{fontSize:18, marginVertical:2, marginHorizontal:8, textAlign:'center'}}>{detalle.valor_detalle}</Text>
<Icon type='AntDesign' name='checkcircle' style={{fontSize: 20, color:detalle.color}}/>
</View>
</TouchableWithoutFeedback>
))}
</View>)
}
return (
<View>
<Text>List One</Text>
<View>
{ details && generateDetailValues('list_one', details)}
</View>
<Text>List two</Text>
<View>
{details && generateDetailValues('list_two', details)}
</View>
</View>
)
}
In summary, I make an API call and after, I render the component returned by "generateDetailValues", this component have a "onPress" function which changes the color of the icon inside the component.
The problem is when I'm press that touchable, the values of "details" change, but the color doesn't , that's because the "generateDetailValues" doesn't executed.
Any idea how I can make that function execute (in the render method) when "details" change?
Thanks in advance.
getIconColor always returns Colors.primary no matter what value you pass into the function, I guess this is why the color never changes.
I don't know how you your Color object works but it seems like it should depend on value in some way, e.g:
const getIconColor = (value) => {
if(!value) return 'grey';
return Colors.value.primary
}

Undefined Array(but its not) when retrieved from firebase realtime database

I am trying to retrieve the data from firebase realtime database and then pass it another function.
But it throws an error saying
TypeError: undefined is not an object (evaluating 'notesList.map')
function NotesList() {
const[notesList, SetNotesList] = useState();
useEffect(() => {
const NoteRef = firebase.database().ref('localnotes-data');
NoteRef.on('value', (snapshot)=> {
const notes = snapshot.val();
const container = [];
for(let id in notes){
container.push({id, ...notes[id] });
}
if (container){
SetNotesList(container);
}
}, []);
})
console.log(notesList);
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesList.map((note) => (<Note text = {text} date = {date}/>))}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
list: {
marginTop: 0,
marginBottom: 145,
}
})
export default NotesList
This is console.log(notesList)
Because noteList is undefined -> const[notesList, SetNotesList] = useState();
So check that noteList exists to call map when you asynchronous fetch is finished.
You can also initialise you noteList like so -> const[notesList, SetNotesList] = useState([]);
The following code should work tho.
...
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesList && notesList.map((note) => (<Note text = {text} date = {date}/>))}
</ScrollView>
</View>
)
}
...
As an alternative to #Dharmaraj's answer, you can also introduce a "loading" variable like so:
Note: Make sure to take a look at the other changes like some variable names, using DataSnapshot#forEach() to maintain the order from the query, detaching the snapshot listeners, snapshot error-handling and making sure the key property is set in the map() function.
let renderCount = 0; // just for debugging, remove it later
function NotesList() {
const [notesList, setNotesList] = useState();
const notesLoading = notesList === undefined;
useEffect(() => {
const notesQueryRef = firebase.database()
.ref('localnotes-data');
// you can add `orderByChild()`, etc. to the above query
const listener = notesQueryRef.on(
'value',
(snapshot) => {
const notesArray = [];
snapshot.forEach(noteSnapshot =>
const id = noteSnapshot.key;
notesArray.push({
id: noteSnapshot.key,
...noteSnapshot.val()
});
);
setNotesList(notesArray);
},
(error) => {
// TODO: Handle errors better than this
console.error("Failed to get notes: ", error);
}
);
// return cleanup function
return () => {
notesQueryRef.off('value', listener);
};
}, []);
// just for debugging, remove it later
console.log({
renderCount: ++renderCount,
notesLoading,
notesList
});
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesLoading
? <Spin tip="Loading..." key="loading" />
: notesList.map(note => (<Note
text={note.text}
date={note.date}
key={note.key}
/>));
}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
list: {
marginTop: 0,
marginBottom: 145,
}
})
export default NotesList
I think that's because initial state of notesList is undefined. Try setting that to an empty array.
const [notesList, SetNotesList] = useState([]);
^^
Now notesList is defined and you can run the map() method on it.

React Native: change State from different component

I'm quite new to React-Native. I have a screen, that is getting information from my service in json format and is displaying the data after that. On that screen, I have a component "Calendar", from which the user can pick up another date. I don't know how to update the predictions-state from that component.
Here is my HomeScreen:
export default function HomeScreen({ navigation }) {
const [predictions, setPredictions] = useState([]);
const [params, setParams] = useState({
lang: 'en',
date: '2020-10-11',
sport: 'soccer'
});
useEffect( () => {
loadPredictions();
}, []);
const loadPredictions = async () => {
const response = await PredictionsApi.getPredictions({params});
// console.log(response.data);
setPredictions(response.data);
}
return (
<View style={styles.main}>
<View style={styles.container}>
<Calendar />
...
</View>
</View>
}
Here is my Calendar Component:
function renderDates({props}) {
const dates = [];
for(let i=-2;i<4;++i) {
var currentDate = new Date(new Date().getTime() + 24 * 60 * 60 * i * 1000);
dates.push(
<TouchableOpacity style={styles.dates} onPress={()=> props.setPredictions({
lang: 'en',
date: '2020-02-01',
sport: 'tennis',
})
}>
<Text style={styles.selected}>{Moment(currentDate).format('ddd')}{"\n"}{Moment(currentDate).format('DD')}</Text>
</TouchableOpacity>
);
}
return dates;
}
export default function Calendar({props}) {
return (
<View style={styles.calendarContainer}>
...
<View style={styles.second}>
{renderDates({props})}
</View>
</View>
);
}
So there are quite a few things I would recommend you do differently in this code. However, to answer your question, you can pass the setPredictions function down as a prop, and then call it with props.setPredictions() in your Calendar component.
A simple example of this idea would be:
const Parent = () => {
const parentFunction = () => console.log('Hello from parent');
return <Child parentFunction={parentFunction} />;
};
// clicking the div in this child will call the function defined in Parent
const Child = ({parentFunction}) => <div onClick={parentFunction}>Click me</div>;
You can use this same principle for all of your functions that set state in the parent as well.
I'll stop here. If you have questions about how this applies to your code just ask. And if you want further advice on the rest of your code, let me know. Thanks.
Here is my attempt to address some of the concerns I have with your code. I think your issue was caused by incorrect destructuring of props, and also that you didn't actually seem to be using setting your predictions anywhere in the child components, or calling your API. I hope this makes sense:
// use const instead of function - more conventional
const HomeScreen = ({navigation}) => {
const [predictions, setPredictions] = useState([]);
const [params, setParams] = useState({
lang: 'en',
date: '2020-10-11',
sport: 'soccer',
});
useEffect(() => {
loadPredictions();
}, []);
const loadPredictions = async () => {
const response = await PredictionsApi.getPredictions({params});
// console.log(response.data);
setPredictions(response.data);
};
return (
<View style={styles.main}>
<View style={styles.container}>
{/* Pass load predictions and setParams to Calendar as props */}
<Calendar loadPredictions={loadPredictions} setParams={setParams} />
</View>
</View>
);
};
// destructuring should be used to get the individual props. If you put {props} then
// the implication is you would be using `props.props.theActualProp`
// so either remove the curly braces or destructure the actual props
// you want to use
const Calendar = props => (
<View style={styles.calendarContainer}>
<View style={styles.second}>
{/* pass all the props down to Dates component */}
{/* I also changed this to an element, since there is no reason to do otherwise */}
<Dates {...props} />
{/* I added a button here so you can actually reload the predictions */}
<TouchableOpacity onPress={props.loadPredictions}>Load new predictions</TouchableOpacity>
</View>
</View>
);
// only destructure if you are pulling individual props OUT of the `props` object
const Dates = props => {
// don't use for loops inside a function like this... it's cleaner to use the `map` method
// on an array
// cache the current time once per render if you need to
const cachedTime = new Date().getTime();
// I'm not sure why these particular numbers are important, or what you're trying to do here
// I've preserverd the behaviour from your for loop, but depending on what you're trying
// to achieve there is probably a more sensible solution to this
return [-2, -1, 0, 1, 2, 3, 4].map(val => {
const rowDate = new Date(cachedTime + 24 * 60 * 60 * val * 1000);
return (
<TouchableOpacity
// add a key when mapping over an array
key={val}
style={styles.dates}
onPress={() =>
// I changed this to setParams. I think this is what you meant, since
// setPredictions should only be form your API response?
props.setParams({
lang: 'en',
date: '2020-02-01',
sport: 'tennis',
})
}
>
<Text style={styles.selected}>
{Moment(rowDate).format('ddd')}
{'\n'}
{Moment(rowDate).format('DD')}
</Text>
</TouchableOpacity>
);
});
};
You should use setState and read the documentation firstly
https://reactjs.org/docs/react-component.html#setstate

Resources