I have a watchlist of bookings for movies and when I click on a button it deletes the booking from the Firestore database. This is the code I have:
const WatchList = () => {
const uid = auth.currentUser.uid;
const docRef = doc(db, 'users', uid);
const [user, setUser] = useState({});
const [watched, setWatched] = useState(true);
const [text, setText] = useState('Watched movies');
const [filteredBookings, setFilteredBookings] = useState(bookings);
const bookingsRef = collection(db, "booking");
const [bookings, setBookings] = useState({});
useEffect(() => {
getUser();
getBookings();
},[])
const getUser = async () => {
const snap = await getDoc(docRef)
setUser({user, ...snap.data()})
}
const getBookings = async () => {
const q = query(bookingsRef, where("users","array-contains",auth.currentUser.uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const a = [];
querySnapshot.forEach((doc) => {
a.push(doc.data());
});
setBookings(querySnapshot.docs);
});
}
const deleteBooking = async(id) => {
try {
await deleteDoc(doc(db, 'booking', id));
}
catch(e) {
console.log(e)
}
console.log('deleted:' + id)
}
return (
<View>
<View>
<Text>{text}</Text>
</View>
<FlatList
data = {filteredBookings}
numColumns = {1}
renderItem = {({item}) => (
<View>
<View>
<Text>{item.data().movie}</Text>
<Text>{item.data().day} - {item.data().showtime}</Text>
</View>
<View>
<TouchableOpacity onPress = {() => {deleteBooking(item.id)}}>
<Text>Delete</Text>
</TouchableOpacity>
</View>
</View>
)}
/>
</View>
)
}
export default WatchList`
How do I get the FlatList to rerender after I press the button? When I refresh the whole app I see that it changes, but I'd like to have it rerendered after the onPress action. Thank you!
As far as I see from your implementation as data for FlatList you use filteredBookings but you set in only once as an initial state for useState hook. You need to update your list of filteredBookings every time you get books from the API like this:
useEffect(() => {
// filter returns new array object
const resultOfFiltering = bookings.filter((booking) => place your condition here)
setFilteredBookings(resultOfFiltering)
}, [bookings])
Related
I have a watchlist of bookings for movies and when I click on a button for one of the FlatList rendered components I'd like to pass the object to the delete function and then to remove it from Firestore. I'm kind of stuck on how to do this. This is what I have so far:
const WatchList = () => {
const uid = auth.currentUser.uid;
const docRef = doc(db, 'users', uid);
const [user, setUser] = useState({});
const [watched, setWatched] = useState(true);
const [text, setText] = useState('Watched movies');
const [filteredBookings, setFilteredBookings] = useState(bookings);
const bookingsRef = collection(db, "booking");
const [bookings, setBookings] = useState({});
useEffect(() => {
getUser();
getBookings();
},[])
const getUser = async () => {
const snap = await getDoc(docRef)
setUser({user, ...snap.data()})
}
const getBookings = async () => {
const q = query(bookingsRef, where("users","array-contains",auth.currentUser.uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const a = [];
querySnapshot.forEach((doc) => {
a.push(doc.data());
});
setBookings(querySnapshot.docs);
});
}
const deleteBooking = (item) => {
console.log(item.data.().title)
}
return (
<View>
<View>
<Text>{text}</Text>
</View>
<FlatList
data = {filteredBookings}
numColumns = {1}
renderItem = {({item}) => (
<View>
<View>
<Text>{item.data().movie}</Text>
<Text>{item.data().day} - {item.data().showtime}</Text>
</View>
<View>
<TouchableOpacity onPress = {() => {deleteBooking(item)}}>
<Text>Delete</Text>
</TouchableOpacity>
</View>
</View>
)}
/>
</View>
)
}
export default WatchList`
I've been trying to pass an item to display it in the console log to see if I got the right one first, but it's not working, so I'd really appreaciate some pointers. Thank you!
In your delete button's onPress method which you have defined as deleteBooking() you will get index also. you can rewrite as deleteBooking(item,index). and now in your definition use splice method of array. e.g. arrayName.splice(index,1) this will delete the record of given index.
For your array it should be like bookings.splice(index,1). Here 1 represents how many record you want to delete from given index.
Let me know doest it works for you or not.
I am using a functional component to render a flatlist, and when the list needs to re render it is doing so ineffeciently. I have tried to format the code better and implement React.memo, useMemo, useCallback, but I keep failing. I thought I implemented React.memo here but I must be doing something wrong, because I still get the warning in the terminal that "You have a large list that is slow.."
Any help implementing the proper methods to prevent unnecessary re renders would be greatly appreciated.
const MemoRender = React.memo(() => {
const renderItem = ({item, index}) => {
return (
<HStack space={2}>
<Center >
<Text >
{'Serial : '}
{item.Serial}
</Text>
<Text>
{'Type : '}
{item.Type}
</Text>
</Center>
<Center >
<Text>
{'Brand : '}
{item.Brand}
</Text>
<Text>
{'Model : '}
{item.Model}
</Text>
<Text>
{'Room : '}
{item.Room}
</Text>
</Center>
<Center>
<Button transparent icon onPress={() => editUser(item)}>
<Icon as={Ionicons} active name="create" />
</Button>
<Button transparent onPress={() => deleteUser(item)}>
<Icon as={Ionicons} active name="trash" />
</Button>
</Center>
</HStack>
);
};
const memoizedValue = useMemo(() => renderItem, [users]);
const [page,setPage] = useState(1);
const [users, setUsers] = useState([]);
const fetchMoreData = () => {
setPage((prev) => prev + 1);
}
useEffect(() => {
console.log('mounted');
//const userRef = firebase.database().ref('/users');
const userRef = firebase.database().ref('/users' + '/C Room');
const OnLoadingListener = userRef.on('value', (snapshot) => {
//setUsers([]);
const list = [];
snapshot.forEach(function (childSnapshot) {
list.push(childSnapshot.val());
});
setUsers(list);
});
const childRemovedListener = userRef.on('child_removed', (snapshot) => {
// Set Your Functioanlity Whatever you want.
//alert('Child Removed');
});
const childChangedListener = userRef.on('child_changed', (snapshot) => {
// Set Your Functioanlity Whatever you want.
// alert('Child Updated/Changed');
});
return () => {
userRef.off('value', OnLoadingListener);
console.log('unmounting...');
//userRef.off('child_removed', childRemovedListener);
// userRef.off('child_changed', childChangedListener);
};
}, []);
useEffect(()=> {
if(page){
}
},[page]);
const deleteUser = (Item) => {
firebase.database()
.ref('users/' + '/C Room'+ '/' + Item.Id)
.remove()
.then(() => {})
.catch((err) => {
console.log(err);
});
};
const deleteAllUsers = () => {
firebase.database()
.ref('users')
.remove()
.then(() => {
setUsers([]);
});
};
return (
<FlatList
data={users}
style={styles.scrollView}
renderItem={memoizedValue}
keyExtractor={(item,index) => index.toString()}
extraData={users}
onEndReachedThreshold={0.3}
onEndReached={fetchMoreData}
contentContainerStyle={{padding: 5,backgroundColor: "green"}}
getItemLayout={(_, index) => ({
length: 60 + 20, // WIDTH + (MARGIN_HORIZONTAL * 2)
offset: (60 + 20) * (index), // ( WIDTH + (MARGIN_HORIZONTAL*2) ) * (index)
index,})}
/>
)
}, [])
Shopify just released new package that implements Virtualised List and replacing FlatList with better performance. It is following best practices of optimisation. Please take a look and test: https://shopify.github.io/flash-list/
I'm developing a cart system and the problem is that, when I add a product to the cart, it works in context and localStorage; but, when I refresh, the data is gone.
export const DataContext = createContext();
export const DataProvider = ({ children }) => {
const [cart, setCart] = useState([]);
const [state, dispatch] = useReducer(AppReducer, cart);
useEffect(() => {
const cartData = JSON.parse(localStorage.getItem("cart"));
if (cartData) {
setCart(cartData);
}
}, []);
useEffect(() => {
localStorage.setItem("cart", JSON.stringify(cart));
// Cookies.set("cart", cart, { expires: 1 / 24 });
// let products = Cookies.get("cart");
// console.log(products);
}, [cart]);
const addToCart = (newProduct) => {
setCart((prev) => [...prev, newProduct]);
};
return (
<DataContext.Provider value={{ cart, setCart, addToCart }}>
{children}
</DataContext.Provider>
);
};
Then I just import addToCart function in my product detail page and give the product as parameter.
Dealing with this in Next.JS is so much worse than normal React. I'll be glad to know what I'm doing wrong.
Your localstorage has been writen when you reload the page. Try the following way to prevent set item in localstorage when init.
const initialState = [];
const [cart, setCart] = useState(initialState);
useEffect(() => {
const cartData = JSON.parse(localStorage.getItem("cart"));
if (cartData) {
setCart(cartData);
}
}, []);
useEffect(() => {
if (cart !== initialState) {
localStorage.setItem("cart", JSON.stringify(cart));
}
}, [cart]);
This is my screen that the user can visit to edit/update their details. My problem is I can't seem to get the values from the Inputs to be used as the new data when the save button is pressed. It complains about being a function. How to convert my state to a string for the function to update the firebase document?
const UserProfileEditScreen = ({ navigation }) => {
const user = auth.currentUser;
const [userData, setUserData] = useState({});
const getUser = async() => {
const currentUser = await firestore
.ref('users')
.doc(user.uid)
.get()
.then((documentSnapshot) => {
if( documentSnapshot.exists ) {
setUserData(documentSnapshot.data());
}
})
}
const [displayName, setNewDisplayName] = useState('');
const [name, setName] = useState('');
const [surname, setSurname] = useState('');
const [birthdate, setBirthdate] = useState('');
const handleUpdate = () => {
firestore.collection('users').doc(user.uid).set({
displayName: setNewDisplayName,
name: setName,
surname: setSurname,
}).then(() => {
console.log('User Updated!');
Alert.alert(
'Profile Updated!',
'Your profile has been updated successfully.'
);
});
navigation.navigate('Profile');
}
useEffect(() => {
getUser();
}, [])
return (
<DismissKeyboard>
<View style={styles.container}>
<View style={styles.insideContainer}>
<Text style={[styles.headTextH2, styles.whiteText]}>Edit Account</Text>
<View style={{marginBottom:45}}></View>
<Text style={[styles.headTextH3, styles.whiteText]}>Display Name</Text>
<TextInput
style={styles.inputStyles}
placeholder={userData.displayName}
placeholderTextColor="#000"
value={displayName}
onChangeText={setNewDisplayName}
/>
<Text style={[styles.headTextH3, styles.whiteText]}>Name</Text>
<TextInput
style={styles.inputStyles}
placeholder={userData.name}
placeholderTextColor="#000"
value={name}
onChangeText={setName}
/>
<Text style={[styles.headTextH3, styles.whiteText]}>Surname</Text>
<TextInput
style={styles.inputStyles}
placeholder={userData.surname}
placeholderTextColor="#000"
value={surname}
onChangeText={setSurname}
/>
<OGButton title="Save Changes" onPress={()=> handleUpdate()} />
</View>
</View>
</DismissKeyboard>
)
}
Error Message:
setName, setSurname are functions that are used to set the value of name and surname respectively and you are passing them in Firestore's set method.
const [displayName, setNewDisplayName] = useState('');
const [name, setName] = useState('');
const [surname, setSurname] = useState('');
const [birthdate, setBirthdate] = useState('');
Instead you should pass the values like this:
firestore.collection('users').doc(user.uid).set({
displayName, name, surname
})
I know that I can reauthenticate with email like this.
var user = firebase.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential(
user.email,
'yourpassword'
);
user.reauthenticateWithCredential(credentials);
But how can I reauthenticate if I use phoneNumber as my sign in method?
It is very similar to how you sign in with phone number. You can do it in 2 ways:
Using reauthenticateWithPhoneNumber:
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
container, parameters, firebase.app());
recaptchaVerifier.render();
user.reauthenticateWithPhoneNumber(user.phoneNumber, recaptchaVerifier)
.then((confirmationResult) => {
return confirmationResult.confirm(prompt('Enter your SMS code'));
})
.then((userCredential) => {
// User successfully reauthenticated.
});
Using reauthenticateWithCredential:
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
container, parameters, firebase.app());
recaptchaVerifier.render();
const phoneAuthProvider = new firebase.auth.PhoneAuthProvider(auth);
phoneAuthProvider.verifyPhoneNumber(user.phoneNumber, recaptchaVerifier)
.then((verificationId) => {
const credential = firebase.auth.PhoneAuthProvider.credential(
verificationId, prompt('Enter your code'));
return user.reauthenticateWithCredential(credential);
})
.then((userCredential) => {
// User successfully reauthenticated.
});
In React Native using expo-firebase-recaptcha
import React, { useState, useEffect, useRef } from 'react';
import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import * as firebase from 'firebase';
import { FirebaseRecaptchaVerifierModal } from "expo-firebase-recaptcha";
const firebaseConfig = {
// your firebaseConfig object
//...
};
const Reauthentication = () => {
const recaptchaRef = useRef();
const [code, setCode] = useState("");
const [verificationId, setVerificationId] = useState();
useEffect(() => {
(async () => {
const user = firebase.auth().currentUser;
const result = await user.reauthenticateWithPhoneNumber(user.phoneNumber, recaptchaRef.current);
setVerificationId(result);
})()
}, []);
const reauthenticate = () => {
verificationId.confirm(code)
.then((userCredential) => {
// User is reauthenticated
})
.catch((err) => {
Alert.alert(err);
})
}
return (
<View style={ styles.container }>
<FirebaseRecaptchaVerifierModal
ref={ recaptchaRef }
firebaseConfig={ firebaseConfig }
/>
<TextInput
keyboardType="numeric"
value={ code }
onChangeText={ (text) => setCode(text) }
/>
<TouchableOpacity onPress={ reauthenticate }>
<Text>Reauthorize</Text>
</TouchableOpacity>
</View>
)
}
export default Reauthentication;