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

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

Related

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

Trying to display values from 2 text inputs

I'm trying to display values from 2 text input and an image array (expo image picker) every time the text and images have been added like a todo app. I get no errors when i add the data, but the display view is empty (does not show any data added). here's the code:
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [photoArray, setPhotoArray] = useState([]);
const [storeItems, setStoreItems] = useState([]);
const addItem = [
name,
price
]
const addStoreItem = () => {
setStoreItems([...storeItems, addItem])
setName(null);
setPrice(null);
};
const deleteItem = (index) => {
let removeItem = [...storeItems, addItem];
removeItem.splice(index, 1);
setStoreItems(removeItem);
}
const pickPhoto = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.2,
});
if (!result.cancelled) {
setPhotoArray([...photoArray, result.uri]);
}
};
return (
<View style={styles.container}>
<View style={styles.storeInput}>
<TouchableOpacity onPress={addStoreItem}>
<View style={styles.iconContainer}>
<Image source={icons.add} style={styles.icon}/>
</View>
</TouchableOpacity>
<View >
<FormStoreInput
placeholder={'Item Name'}
onChange={setName}
value={name}
/>
<FormStoreInput
placeholder={'Item Price'}
onChange={setPrice}
value={price}
/>
</View>
</View>
<View>
And i map the data here:
{
storeItems.map((_, index) => {
return (
<AddStoreItem key={index} name={name} price={price}/>
)
})
}
I'm new to this so please help.
The reason it's not showing up is because you aren't doing anything with the data in your map function. Map function signature is map(value, index). In your case, your map function should be something like so
{
storeItems.map((value, index) => {
const [name, price] = value
return (
<AddStoreItem key={index} name={name} price={price}/>
)
})
}
The reason for destructuring value is because your storeItems is an array of arrays, and you can't just use the array directly.

How to render item when data is updated react native

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

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.

Possible Unhadled Promise Rejection on getting dara from API

export default function App({ navigation, navigation: { goBack } }) {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [data, setData] = useState("");
const handleBarCodeScanned = ({ data }) => {
setScanned(true);
axios
.get(
`https://api.edamam.com/api/food-database/v2/parser?upc=${data}&app_id=2626c70d&app_key=0c0f87ae4e5437621363ecf8e7ea80ae&page=20`
)
.then((res, data) => {
setData(res.data.hints);
navigate("Food", {
title: data.label, <--------------------------
});
})
.catch((error) => {
console.log(error.response.data);
});
};
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
>
<TouchableOpacity onPress={() => goBack()}>
<Icon
name="angle-left"
color="white"
size={40}
style={{ top: hp("10%"), left: wp("5%") }}
/>
</TouchableOpacity>
</BarCodeScanner>
</View>
);
}
Hello, I'm trying to send title to a page using React Navigation, so when the user scan an item, and the item it's found in the database it'll take him/her on the Food page with that constant. Currently the data is obtained from the database correctly, but when I run the code it says Possible Unhadled Promise Rejection: TypeError: undefined is not an object
you are using the same name data for a state, inside a function and again inside .then of the API try changing the names and then check it, I didn't figure out which data.label is you passing to the title please use different names for the variable inside the function ({dataFromBarCode}) and then (res, dataApi) it will work

Resources