UseEffect dependency array keeps running even state does not change - javascript

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).

Related

How to add button in React Native Flatlist?

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;

How can I make .filter work properly on React-Native?

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

useState array is undefined even though it is initialized as empty array

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.

how to use useState on a specific onPress Touchable Opacity

I have a screen that outputs all the groups a user isnt a member of. Each group has a join button that when clicked adds the user to the members subcollection under groups collection in firestore.
The state of the button is supposed to change from join to Joined when a user clicks the join button and then change from joined to join when the user clicks it again.
My problem is that since all the buttons have the same joinedButton state which I am listening to, changes of when a user clicks one button the state of all the buttons changes, while only the clicked one should change.
The buttons are outputted using an array map of the promise received from a firestore query.
Any ideas how I can change the state of only the button that has been clicked?
import { StyleSheet, Text, View, Image } from 'react-native'
import React, { useState, useEffect, useContext } from 'react'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { db } from '../../firebase'
import { AuthContext } from '../../navigation/AuthProvider'
const DiscoverGroupList = ({ navigation }) => {
const [joinedButton, setJoinedButton] = useState(false);
const fetchGroups = async () =>{
//code to
}
const { user } = useContext(AuthContext);
const joinGroup = async (groupId) => {
try {
await db.collection('groups')
.doc(groupId)
.collection('members')
.doc(user.uid)
.set({
userId: user.uid,
isMember: true,
})
setJoinedButton(true)
} catch (error) {
console.log(error)
}
}
const leaveGroup = async (groupId) => {
try {
await db.collection('groups')
.doc(groupId)
.collection('members')
.doc(user.uid)
.delete()
setJoinedButton(false)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
fetchGroups()
}, [joinedButton])
return (
<>
{groupsYouManage.map((item) => (
<View key={item.groupId} style={styles.groupWrapper}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Image source={{ uri: item.groupImage }} style={styles.groupImage} />
<View>
<Text style={styles.groupListTitle}>{item.groupName}</Text>
<Text style={styles.groupMembers}>{item.groupMembers}</Text>
</View>
</View>
{!joinedButton ? (
<TouchableOpacity style={styles.join} onPress={() => joinGroup(item.groupId)}>
<Text style={styles.joinText}>Join</Text>
</TouchableOpacity>
) : (
<TouchableOpacity style={styles.join} onPress={() => leaveGroup(item.groupId)}>
<Text style={styles.joinText}>Joined</Text>
</TouchableOpacity>
)
}
</View>
))}
</>
)
It looks like you're setting a value in the database of members collection with the ID and isMember: true. Is it possible that when you map over the data instead of rendering the button based off of the useState joinedButton, could you set the button to be rendered based on the isMember bool?
{item.isMember ? <leaveGroup button /> : <joinGroupButton />}
I think creating separate state for every item present in the array can help.
import { StyleSheet, Text, View, Image } from 'react-native'
import React, { useState, useEffect, useContext } from 'react'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { db } from '../../firebase'
import { AuthContext } from '../../navigation/AuthProvider'
const DiscoverGroupList = ({ navigation }) => {
const fetchGroups = async () =>{
//code to
}
const { user } = useContext(AuthContext);
const joinGroup = async (groupId) => {
try {
await db.collection('groups')
.doc(groupId)
.collection('members')
.doc(user.uid)
.set({
userId: user.uid,
isMember: true,
})
} catch (error) {
console.log(error)
}
}
const leaveGroup = async (groupId) => {
try {
await db.collection('groups')
.doc(groupId)
.collection('members')
.doc(user.uid)
.delete()
} catch (error) {
console.log(error)
}
}
useEffect(() => {
fetchGroups()
}, [joinedButton])
return (
<>
{groupsYouManage.map((item) => {
const [joinedButton, setJoinedButton] = useState(false);
const handleJoin = () => {
joinGroup(item.groupId)
setJoinedButton(true);
}
const handleLeave = () => {
leaveGroup(item.groupId)
setJoinedButton(false);
}
return (
<View key={item.groupId} style={styles.groupWrapper}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Image source={{ uri: item.groupImage }} style={styles.groupImage} />
<View>
<Text style={styles.groupListTitle}>{item.groupName}</Text>
<Text style={styles.groupMembers}>{item.groupMembers}</Text>
</View>
</View>
{!joinedButton ? (
<TouchableOpacity style={styles.join} onPress={handleJoin }>
<Text style={styles.joinText}>Join</Text>
</TouchableOpacity>
) : (
<TouchableOpacity style={styles.join} onPress={handleLeave}>
<Text style={styles.joinText}>Joined</Text>
</TouchableOpacity>
)
}
</View>
)})}
</>
)

How to use realtime database in react native when loged using firestore?

So I am here to ask if there is a method to use firebase with real-time database in 1 apps.
My login already worked using Firestore.
This is my code that is storing 1 text to my Firestore data, Its worked too.
I want to change it to real-time database whenever there a new data inserted, but still have the id user logged to my react-native app
The point is I want to use Realtime Database instead of Firestore.
Login as Firestore, Save data as Realtime Database
import React, { useEffect, useState } from 'react'
import { FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native'
import styles from './styles';
import { firebase } from '../../firebase/config'
export default function HomeScreen(props) {
const [entityText, setEntityText] = useState('')
const [entities, setEntities] = useState([])
const entityRef = firebase.firestore().collection('entities')
const userID = props.extraData.id
useEffect(() => {
entityRef
.where("authorID", "==", userID)
.orderBy('createdAt', 'desc')
.onSnapshot(
querySnapshot => {
const newEntities = []
querySnapshot.forEach(doc => {
const entity = doc.data()
entity.id = doc.id
newEntities.push(entity)
});
setEntities(newEntities)
},
error => {
console.log(error)
}
)
}, [])
const onAddButtonPress = () => {
if (entityText && entityText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: entityText,
authorID: userID,
createdAt: timestamp,
};
entityRef
.add(data)
.then(_doc => {
setEntityText('')
Keyboard.dismiss()
})
.catch((error) => {
alert(error)
});
}
}
const renderEntity = ({item, index}) => {
return (
<View style={styles.entityContainer}>
<Text style={styles.entityText}>
{index}. {item.text}
</Text>
</View>
)
}
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder='Add new entity'
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setEntityText(text)}
value={entityText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{ entities && (
<View style={styles.listContainer}>
<FlatList
data={entities}
renderItem={renderEntity}
keyExtractor={(item) => item.id}
removeClippedSubviews={true}
/>
</View>
)}
</View>
)
}
Can someone help me and tell me how to do that?
thanks

Categories

Resources