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.
Related
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);
};
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
}
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);
});
}, []);
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.
I'm getting the data from the database and show it in a FlatList. Whenever I add or remove something from the data the data isn't showing correctly in the FlatList.
Whenever I remove something it shows an empty list.
Whenever I add something it only shows the newly added data - nothing else.
I'm using firebase realtime database and use the data I get as follows:
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
// setWords([...data]) doesn't work either.
}
})
My Flatlist looks like this:
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
extraData={words}
/>
When I console.log() the data I always get the data I want to show but the FlatList just won't show it correctly.
It also doesn't work when I use the spread-operator and/or extraData.
Because someone asked for it here is the entire file (I left out the styling and the imports)
const EditList = ({ editKey }) => {
const [wordlist, setWordlist] = useState(0);
const [refresh, setRefresh] = useState(false);
const [words, setWords] = useState([]);
const [wordLoading, setWordLoading] = useState({ loading: false });
const [loading, setIsLoading] = useState(false);
const [btnLoading, setBtnLoading] = useState(false);
const [word, setWord] = useState('');
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords([...data]);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
}
}, [editKey])
const onAdd = () => {
setBtnLoading(true);
firebase.database().ref(`/wordlists/${editKey}/words`).push({ word })
.then(() => {
setBtnLoading(false);
setWord('');
setRefresh(!refresh);
})
}
const onDelete = (key) => {
setWordLoading({ key, loading: true });
firebase.database().ref(`/wordlists/${editKey}/words/${key}`).remove().then(() => {
setWordLoading({ loading: false });
setRefresh(!refresh);
});
}
const renderItem = ({ item }) => (
<ItemWrapper>
<ItemWord>{ item.word }</ItemWord>
<DeleteView onPress={() => onDelete(item.key)}>
{ wordLoading.loading && wordLoading.key === item.key ?
<ActivityIndicator size="small" /> :
<DIcon name="trash-2" size={24} />
}
</DeleteView>
</ItemWrapper>
)
const createData = (words) => {
const data = [];
if (typeof words !== 'undefined') {
Object.keys(words).forEach(key => {
const obj = { key, word: words[key].word };
data.push(obj);
})
}
console.log(data, 'DATADATADATA');
return data;
}
if (editKey === 0) {
return (
<NokeyWrapper>
<NoKeyText>No list selected...</NoKeyText>
</NokeyWrapper>
)
}
if (loading) {
return (
<NokeyWrapper>
<ActivityIndicator size="large" />
</NokeyWrapper>
)
}
return (
<Wrapper
behavior={Platform.OS == "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === 'ios' && 180}
>
<WordListName>{wordlist.listName}</WordListName>
<FlatListWrapper>
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
//extraData={refresh}
extraData={words}
/>
</FlatListWrapper>
<AddWordWrapper>
<SInput value={word} onChangeText={(text) => setWord(text)} />
<Button onPress={() => onAdd()} loading={btnLoading}>
<Feather name="plus" size={24} color="black" />
</Button>
</AddWordWrapper>
</Wrapper>
)
};
export default EditList;
u need to useRef for this instance because the new 'words' is not inside the .on('value') call.
const [words, _setWords] = useState([]);
const wordRef = useRef(words)
//function to update both wordRef and words state
const setWords = (word) => {
wordRef = word
_setWords(word)
}
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
let data = wordRef //create a temp data variable
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
return () => firebase.database().ref(`/wordlists/${editKey}`).off('value') // <-- need to turn it off.
}
}, [editKey, wordRef])
You probably need to change setRefresh etc with the same method if they are not refreshing.
After a lot more tries I found out the problem was somewhere else. Somehow using 'flex: 1' on my in my renderItem() was causing this issue. I actually found this issue also on github: GitHub React Native issues
So after removing 'flex: 1' from the element everything was showing as expected.
// before
const renderItem = ({ item }) => (
<ItemWrapper style={{ flex: 1, flexDirection: row }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)
// after
const renderItem = ({ item }) => (
<ItemWrapper style={{ width: '100%' }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)