I'm working in a React Native Expo project with Firebase v9 and I'm getting an error because of my state variabel categories(I think that's the issue).
This component allows the user to add categories to a flatlist, which is seen here:
As it shows i'm already getting an warning which says: '[Unhandled promise rejection: FirebaseError: Function setDoc() called with invalid data. Unsupported field value: undefined (found in field categories in document users/Hk4k6fKrtZZG1BGffFrOTRvuT2h2)]'
And when i add a category i get the error -> Render error: undefined is not an object(evaluating 'iter[symbol.iterator]')
This is the code for my CategoryComponent:
import { StyleSheet, View, FlatList, Alert, Animated} from 'react-native'
import React, { useState, useEffect} from 'react'
import { db, } from '../../firebase/firebase'
import { doc, setDoc, onSnapshot} from 'firebase/firestore';
import firebase from 'firebase/compat/app';
import { Button, Divider, Subheading, Text, Modal, Portal, TextInput } from 'react-native-paper';
import Swipeable from 'react-native-gesture-handler/Swipeable'
import { TouchableOpacity } from 'react-native-gesture-handler';
import { useNavigation } from '#react-navigation/native';
export default function CategoryComponent() {
const containerStyle = {backgroundColor: 'white', padding: 100, margin: 10};
const [textInput, setTextInput] = useState('');
const [visible, setVisible] = useState(false);
const [categories, setCategories] = useState([])
const navigation = useNavigation();
const [dataFetch, setDataFetch] = useState(false);
useEffect(
() =>
onSnapshot(doc(db, "users", `${firebase.auth().currentUser.uid}`), (doc) => {
setCategories(doc.data().categories)
setDataFetch(true)
}
),
console.log(categories),
[]
);
useEffect(() => {
addToFirebase();
}, [categories])
const showModal = () => {
setVisible(true);
}
const hideModal = () => {
setVisible(false);
}
const categoryNavigate = (item) => {
navigation.navigate("Your Organizer tasks", {item});
}
const addCategory = (textInput) => {
setCategories((prevState) => {
return [
{name: textInput, id: Math.floor(Math.random() * 10000) + 1 },
...prevState
];
})
hideModal();
}
const addToFirebase = async() => {
if(dataFetch) {
await setDoc(doc(db, "users", `${firebase.auth().currentUser.uid}`), {
categories: categories
}, {merge: true});
}
};
const deleteItem = (item) => {
setCategories((prevState) => {
return prevState.filter(category => category.id != item.id)
})
}
const DataComponent = (item) => {
const rightSwipe = (progress, dragX) => {
const scale = dragX.interpolate({
inputRange: [-100, 0],
outputRange: [1, 0],
extrapolate: 'clamp'
});
return(
<TouchableOpacity activeOpacity={0.8} onPress={() => deleteItem(item)}>
<View>
<Animated.Text style={[styles.deleteItem, {transform: [{scale}]}]}>Delete</Animated.Text>
</View>
</TouchableOpacity>
)
}
return (
<TouchableOpacity onPress={() => categoryNavigate(item)}>
<Swipeable renderRightActions={rightSwipe}>
<View>
<Text>{item.name}</Text>
</View>
</Swipeable>
</TouchableOpacity>
)
}
return (
<View>
<Subheading>Your categories</Subheading>
<View>
<FlatList
style={styles.flatList}
keyExtractor={(item) => item.id}
data={categories}
renderItem={ ({item}) => (
<DataComponent {...item}/>
)}
/>
</View>
<View>
<Button mode="contained" uppercase={false} onPress={showModal}>
Add a category
</Button>
</View>
<Portal>
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={containerStyle}>
<Text>Name your category: </Text>
<TextInput placeholder="Enter category name" value={textInput} onChangeText={val => setTextInput(val)}/>
<Button mode="contained" uppercase={false} onPress={() => addCategory(textInput)}>
Add
</Button>
</Modal>
</Portal>
</View>
)
}
I have consol.logged the state variable categories in my useEffect and i don't understand why it shows ''undefined'' when I have initialized it as an empty array, so i would expect to see a empty array in the consol.log for the state variable categories when there is no categories in the flatlist.
If you clearly look there is no such category type key value in the object, so when you perform setCategories(doc.data().categories) it sets the categories value undefined .You can't merge or add a Doc where the field value is undefined.
Related
when calling api returns only red background but not text
I expect the api to return a list of movies displayed on the app interface
but when i return the Items function directly inside the render function, it returns me the text, but when I call the function outside, it just doesn't return the text but just the background
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, FlatList, Text, View} from 'react-native';
import Axios from 'axios';
export default App = () => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
Axios.get('https://reactnative.dev/movies.json')
.then(({data}) => {
setData(data.movies);
})
.catch(error => console.log('error', error))
.finally(() => setLoading(false));
}, []);
const Items = item => {
return (
<View>
<Text style={{flex: 1, backgroundColor: 'red', color: 'blue'}}>
{item.title}, {item.releaseYear}
</Text>
</View>
);
};
return (
<View style={{flex: 1, padding: 24}}>
{isLoading ? (
<ActivityIndicator />
) : (
<FlatList
data={data}
keyExtractor={(item, index) => {
return index.toString();
}}`enter code here`
renderItem={Items}
/>
)}
</View>
);
};
Change your Items component likewise :
const Items = ({item}) => {
return (
<View>
<Text style={{flex: 1, backgroundColor: 'red', color: 'blue'}}>
{item.title}, {item.releaseYear}
</Text>
</View>
);
};
I am trying to create a very simple question-and-answer app.
If I click on the show answer button then the answer should show only where I click, but now all answers are showing when I click on the button. I fetch question answers from Firestore. What is the problem Please check my Firestore data and Flatlist code. please check the images, Thank you in advance for your support
import React, { useState, useEffect } from 'react';
import { ActivityIndicator, FlatList, View, Text, Pressable, Button, StyleSheet } from 'react-native';
import {firebase} from '../config';
const Testing = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('dd11');
const [showValue, setShowValue] = useState(false);
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { QuestionOne, ans, optionOne, optionTwo, optionThree, optionFour
} = doc.data()
users.push({
id: doc.id,
QuestionOne, ans, optionOne, optionTwo, optionThree, optionFour
})
})
setUsers(users)
}
)
}, [])
return (
<View style={{ flex:1,}}>
<FlatList
data={users}
numColumns={1}
renderItem={({item}) => (
<Pressable >
<View>
<View style={{paddingLeft: 10, paddingRight: 10,}}>
{item.QuestionOne && <Text>{item.QuestionOne}</Text>}
{item.optionOne && <Text>{item.optionOne}</Text>}
{item.optionTwo && <Text>{item.optionTwo}</Text>}
{item.optionThree && <Text>{item.optionThree}</Text>}
{item.optionFour && <Text>{item.optionFour}</Text>}
{showValue? item.ans &&<Text style={{color: 'green'}} >{item.ans}</Text> : null}
<Button title="Show Answer" onPress={() => setShowValue(!showValue)} />
</View>
</View>
</Pressable>
)} />
</View>
);}
export default Testing;
you can try this, by maintaining an array of indexes, only those will be shown :)
NOte: also letmeknow if you want that func that if answer is shown and you want to hide it on press again.
import React, { useState, useEffect } from 'react';
import { ActivityIndicator, FlatList, View, Text, Pressable, Button, StyleSheet } from 'react-native';
import {firebase} from '../config';
const Testing = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('dd11');
const [showValue, setShowValue] = useState(false);
const [answerIndexs,setAnsIndex] = useState([])
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { QuestionOne, ans, optionOne, optionTwo, optionThree, optionFour
} = doc.data()
users.push({
id: doc.id,
QuestionOne, ans, optionOne, optionTwo, optionThree, optionFour
})
})
setUsers(users)
}
)
}, [])
const onPressOfShowAnswer = (index) => {
const existingIndexs = [...answerIndexs]
if(!existingIndexs.includes(index)){
existingIndexs.push(index)
}else{
existingIndexs.splice(index,1)
}
setAnsIndex(existingIndexs)
}
return (
<View style={{ flex:1,}}>
<FlatList
data={users}
numColumns={1}
renderItem={({item,index}) => (
<Pressable >
<View>
<View style={{paddingLeft: 10, paddingRight: 10,}}>
{item.Q1 && <Text>{item.QuestionOne}</Text>}
{item.optionOne && <Text>{item.optionOne}</Text>}
{item.optionTwo && <Text>{item.optionTwo}</Text>}
{item.optionThree && <Text>{item.optionThree}</Text>}
{item.optionFour && <Text>{item.optionFour}</Text>}
{ answerIndexs.includes(index) ? item.ans &&<Text style={{color: 'green'}} >{item.ans}</Text> : null}
<Button title="Show Answer" onPress={() => onPressOfShowAnswer(index)} />
</View>
</View>
</Pressable>
)} />
</View>
);}
export default Testing;
I'm trying to display this info on a FlatList once app is loaded, having an input where I can query an item that I would like to check then display it on the same FlatList. The issue is that data={objects.filter(...)} is not working properly, I already received data on console.log('data: ', data); it should be passing the info to objects through setObjects but I think it is not doing it, what could I be missing??
import React, { useState, useEffect } from 'react';
import { Text , TextInput, View, StyleSheet, FlatList } from 'react-native';
import Object from './Object';
export default function Content () {
const [objects, setObjects] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const [search, setSearch] = useState("");
const loadData = async () => {
const res = await fetch(
"https://us.api.blizzard.com/data/wow/search/item?namespace=static-us&name.en_US=Thunderfury&orderby=id&_page=1&access_token=(private token)"
);
const data = await res.json();
console.log('data: ', data);
setObjects(data);
};
useEffect(() => {
loadData();
}, []);
return (
<View style={styles.content}>
<View style={styles.section} >
<Text style={styles.logo}>Input </Text>
<TextInput
style={styles.searchInput}
placeholder="Search an item"
placeholderTextColor="#858585"
onChangeText={(text) => text && setSearch(text)}
/>
</View>
<FlatList
data={objects.filter(
(object) =>
object.name.en_US.toLowerCase().includes(search.toLocaleLowerCase())
)}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => <Object object = {item} style={styles.item}/>}
refreshing={refreshing}
onRefresh={loadData()}
/>
</View>
);
}
I have a useEffect which keeps running in a infinite loop even though my state, which i'm using in my dependecy array, are not changing(or am i missing something about my tasks-state that it is changing somewhere?)
The useEffect is used to query and retrieve data from Firestore, and here is the code:
import { StyleSheet, View, FlatList, Animated } from 'react-native'
import React, {useEffect, useState} from 'react'
import { Subheading, Divider, Text, Modal, Button, Portal, TextInput} from 'react-native-paper';
import Swipeable from 'react-native-gesture-handler/Swipeable'
import { TouchableOpacity } from 'react-native-gesture-handler';
import { collection, where, query, getDocs, addDoc, deleteDoc} from 'firebase/firestore';
import { db} from '../../firebase/firebase'
import firebase from 'firebase/compat/app';
import uuid from "react-native-uuid";
export default function TaskComponent({route}) {
const item = route.params.item;
const containerStyle = {backgroundColor: 'white', padding: 60, margin: 10};
const [tasks, setTasks] = useState({});
const [textInput, setTextInput] = useState({name: "", description: ""});
let userID = `${firebase.auth().currentUser.uid};`
const filteredTasks = [];
useEffect(() => {
const getFilterTasks = async() => {
const q = query(collection(db, 'allTasks'), where('userID', '==', userID), where('categoryID', '==', item.id))
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
filteredTasks.push(doc.data())
})
setTasks(filteredTasks)
}
getFilterTasks();
}, [tasks])
const handleChange = (name, value) => {
setTextInput({
...textInput,
[name]: value,
});
};
const showModal = () => {
setVisible(true);
}
const hideModal = () => {
setVisible(false);
}
const addTask = (textInput) => {
setTasks((prevState) => {
return [
{
userID: userID,
categoryID: item.id,
name: textInput.name,
description: textInput.description,
id: uuid.v1()
},
...prevState
];
})
addToFirebase();
hideModal();
}
const deleteItem = (item) => {
setTasks((prevState) => {
return prevState.filter(task => task.id != item.id)
})
}
const addToFirebase = async() => {
await addDoc(collection(db, 'allTasks'), {
userID: userID,
categoryID: item.id,
name: textInput.name,
description: textInput.description,
id: uuid.v1()
});
}
const DataComponent = (item) => {
const rightSwipe = (progress, dragX) => {
const scale = dragX.interpolate({
inputRange: [-100, 0],
outputRange: [1, 0],
extrapolate: 'clamp'
});
return(
<TouchableOpacity activeOpacity={0.8} onPress={() => deleteItem(item)}>
<View>
<Animated.Text>Delete</Animated.Text>
</View>
</TouchableOpacity>
)
}
return (
<TouchableOpacity>
<Swipeable renderRightActions={rightSwipe}>
<View>
<View>
<Text>Name:</Text>
<Text> {item.name}</Text>
</View>
<View>
<Text>Date:</Text>
<Text> {item.date}</Text>
</View>
<Text>Description:</Text>
<Text>{item.description}</Text>
</View>
</Swipeable>
</TouchableOpacity>
)
}
return (
<View>
<Subheading>Your {item.name} tasks:</Subheading>
<View>
<FlatList
keyExtractor={(item) => item.id}
data={tasks}
renderItem={ ({item}) => (
<DataComponent {...item}/>
)}
/>
</View>
<View>
<Button mode="contained" uppercase={false} onPress={showModal}>
Add a task
</Button>
</View>
<Portal>
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={containerStyle}>
<Text>Name your task: </Text>
<TextInput placeholder="Enter task name" value={textInput.name} onChangeText={(text) => handleChange('name', text)} name="name"/>
<Text>Enter description:</Text>
<TextInput multiline placeholder="Enter description" value={textInput.description} onChangeText={(text) => handleChange('description', text)} name="description"/>
<Button mode="contained" uppercase={false} onPress={() => addTask(textInput)}>
Add
</Button>
</Modal>
</Portal>
</View>
)
}
I have also tried just with an empty dependency array, but then i have to refresh the code everytime i want to see the right data.
Just remove tasks from the triggers array.
Leave an empty array, so the useEffect will be called only at mount.
useEffect(() => {
const getFilterTasks = async() => {
const q = query(collection(db, 'allTasks'), where('userID', '==', userID), where('categoryID', '==', item.id))
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
filteredTasks.push(doc.data())
})
setTasks(filteredTasks)
}
getFilterTasks();
}, [])
I know I am late to the party, but inside the useEffect you are calling getFilterTasks() which is also calling setTasks(filteredTasks) which is basically changing your tasks object again and therefore triggers the useEffect again. This is the reason for your infinite loop.
What you want here is not an useEffect when you are adding, editing or removing a task, but rather a reusable function to call after the operation is complete to update the tasks. (an option would be getting the getFilterTasks function outside of useEffect and calling it after await addDoc(collection(db, 'allTasks'), {...}) inside your const addToFirebase function).
Here's the function-
const setLoading = (value) => {
const messages = dashboards.data.message.filter((item) => {
const title = item.dashboardTitle || item.dashboardName;
return title.toLowerCase().startsWith(value.toLowerCase());
});
setFiltered(messages);
console.log(filtered);
};
I want to display the variable 'messages' separately in my app, how would I do that? 'messages' variable needs to be displayed within the default react native 'Text' component. I have written down 'messages' below within Text component but currently it's not displaying anything (since it is within function) -
import React, { useState, useEffect, useReducer } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, Keyboard} from 'react-native';
import { Searchbar } from 'react-native-paper';
import { theme } from '../theme';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { apiStateReducer } from '../reducers/ApiStateReducer';
import CognitensorEndpoints from '../services/network/CognitensorEndpoints';
import DefaultView from '../components/default/DefaultView';
import DashboardListCard from '../components/DashboardListCard';
const AppHeader = ({
scene,
previous,
navigation,
searchIconVisible = false,
}) => {
const [dashboards, dispatchDashboards] = useReducer(apiStateReducer, {
data: [],
isLoading: true,
isError: false,
});
const [filtered, setFiltered] = useState([]);
const setLoading = (value) => {
const messages = dashboards.data.message.filter((item) => {
const title = item.dashboardTitle || item.dashboardName;
return title.toLowerCase().startsWith(value.toLowerCase());
});
setFiltered(messages);
console.log(filtered);
};
const dropShadowStyle = styles.dropShadow;
const toggleSearchVisibility = () => {
navigation.navigate('Search');
};
useEffect(() => {
CognitensorEndpoints.getDashboardList({
dispatchReducer: dispatchDashboards,
});
}, []);
return (
<>
<View style={styles.header}>
<View style={styles.headerLeftIcon}>
<TouchableOpacity onPress={navigation.pop}>
{previous ? (
<MaterialIcons
name="chevron-left"
size={24}
style={styles.visible}
/>
) : (
<MaterialIcons
name="chevron-left"
size={24}
style={styles.invisible}
/>
)}
</TouchableOpacity>
</View>
<Text style={styles.headerText}>
{messages}
</Text>
<View style={styles.headerRightIconContainer}>
{searchIconVisible ? (
<TouchableOpacity
style={[styles.headerRightIcon, dropShadowStyle]}
onPress={toggleSearchVisibility}>
<MaterialIcons name="search" size={24} style={styles.visible} />
</TouchableOpacity>
) : (
<View style={styles.invisible} />
)}
</View>
</View>
</>
);
};
If your messages variable is an array you can map it
{messages.map((message, key)=>(
<Text style={styles.headerText}>
{message.dashboardName}
</Text>
))}
Since your messages variable is stored in 'filtered' state, you can map it by doing this:
{filtered.map((item, index) => <Text key={index}>{item.dashboardName}<Text>)}