I got this simplified code
class Request extends Component {
constructor(props){
super(props);
this.Remove = this.Remove.bind(this)
}
componentWillMount(){
firebase.database().ref('/Users/' + AuthID).orderByChild('UserRequests').on("value", function(snapshot) {
snapshot.forEach((child) => {
Name = child.val().Name;
UserID = child.val().UserID;
obj = {key: keyIndex++, Name: Name, ID: UserID};
Requests.push(obj)
});
self.setState({Array: Requests});
});
}
//Need to call map func again as soon as user use Remove func and remove data
RenderUsers = () => {
return this.state.Array.map((Data) => (
<View key={Data.key}>
<TouchableHighlight onPress={() => {
this.Remove(Data.ID, Data.Name, Data.key);
}}>
<Text> Remove </Text>
</TouchableHighlight>
</View>
))
};
Remove = (ID, Name, KeyIndex) => {
this.state.Array.splice(KeyIndex, 1);
firebase.database().ref('Users/' + AuthID + '/UserRequests/').child(ID).remove();
//this.RenderUsers();
};
render(){
<View>
{this.RenderUsers()}
</View>
}
}
Let's say map function rendered 10 views. As soon as user clicked on a TouchableHighlight it removes from Firebase right View (request) and it doesn't exist anymore. Unfortunately I can't re-map array after data are deleted. I tried many ways but nothing worked. I thought problem is that I get data in componentWillMount. So it can't get data from Firebase after first mount anymore but if I add Request to database when app is running on that screen it appears immediately. Problem shouldn't be in componentWillMount function.
Related
What I'm trying to do is to display in a list all the clients.
When there is no client in the list, it is another view that is displayed which says no client exists.
The initial value of the state its an empty array.
So when the axios.get() method gets the data from the backend, two times its called the initial value(which is an empty array), and after its filled with the client list that comes from backend.
const [clientList, setClientList] = useState([])
useEffect(() => {
axios
.get(`api/${phone}`)
.then(setClientList(response.data.data))
})
.catch((error) => console.log(error));
}
});
}, [clientList]);
console.log(clientList)
return(
{clientList.length > 0? (
<View>
<FlatList
data={clientList}
renderItem={(item) => {
let client= item.item.fullName;
return (
<View >
<Text>{client}</Text>
/>
</View>
)
}}
/>
</View>
) : (
<Text> No Client List </Text>
)}
)
When I console the clientList it will show:
clientList[]
clientList[]
clientList[
{
fullName: John Doe
},
{
fullName: Nick Smith
}
]
The first two empty arrays (that comes from the initial value of the useState) it will show the No Client List every time the user goes to the client list screen or reload the screen.
How can I prevent showing the <Text> No Client List </Text> when the clientList has clients on the array?
you can add a new state isLoading which will be true by default to handle the clientList initial empty array case.
const [isLoading, setIsLoading] = useState(true)
...
useEffect(() => {
axios
.get(`api/${phone}`)
.then(() => {
setIsLoading(false)
setClientList(response.data.data)
})
.catch(() => {
setIsLoading(false)
});
}, []);
...
// you can conditionally return UI based on `isLoading` or you can use `ListEmptyComponent` prop from FlatList along with `isLoading`
if(isLoading) {
return(<Text>loading</Text>)
}
// remaining code
return(
...
I'm using React Native (0.68) and Firebase RTDB (with the SDK, version 9), in Expo.
I have a screen that needs to pull a bunch of data from the RTDB and display it in a Flatlist.
(I initially did this without Flatlist, but initial rendering was a bit on the slow side.)
With Flatlist, initial rendering is super fast, huzzah!
However, I have an infinite loop re-render that I'm having trouble finding and fixing. Here's my code for the screen, which exists within a stack navigator:
export function GroupingsScreen () {
... set up a whole bunch of useState, database references (incl groupsRef) etc ...
onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
if (loaded == false) {
console.log('--start processing')
setLoaded(true);
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
console.log('--done processing')
setGroupsArray(newObject)
}
});
.... more stuff excerpted ....
return (
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)};
I'm using loaded/setLoaded to reduce re-renders, but without that code, RN immediately dumps me out for excessive re-renders. Even with it, I get lots of extra renders.
So...
Can someone point me at what's triggering the rerender? The database is /not/ changing.
Is there a better way to get RTDB info into a Flatlist than the code I've written?
I have some code that actually does change the database. That's triggering a full rerender of the whole Flatlist, which is visibly, painfully slow (probably because parts are actually rendering 10x instead of once). Help?
For completeness, here's the OneItem code, so you can see just how complex my Flatlist items are:
const OneItem = (data) => {
// console.log('got data',data)
return (
<View style={[styles.rowView, { backgroundColor: data.sku?'white': '#cccccc'}]} key={data.name}>
<TouchableOpacity style={styles.nameView} onPress={() => {
navigation.navigate('AddEditItemScreen', {purpose: 'Grouping', itemname: data.name, parent: data.parent, mode: 'fix'})
}}>
<View style={styles.nameView}>
<Text style={styles.itemtext}>{data.name}</Text>
{data.sku? null: <Text>"Tap to add SKU."</Text>}
{data.size?<Text>{data.size} </Text>: <Text>no size</Text>}
</View>
</TouchableOpacity>
<View style={styles.buttonView}>
<Button style={styles.smallButton}
onPress={() => { changeQuant(data.quantity ? data.quantity - 1 : -1, data.parent + '/ingredients/' + data.name) }}
>
{data.quantity > 0 ? <Text style={[styles.buttonText, { fontSize: 20 }]}>-</Text>
:<Image source={Images.trash} style={styles.trashButton} />}</Button>
<Text style={styles.quantitytext}>{data.quantity}</Text>
<Button style={styles.smallButton}
onPress={() => {
changeQuant(data.quantity? data.quantity +1 : 1, data.parent+'/ingredients/'+data.name)}}>
<Text style={[styles.buttonText, {fontSize: 20}]}>+</Text></Button>
</View>
</View>
)
};```
I worked out how to stop the rerender (question #1). So, within my Screen functional component, I needed to make another function, and attach the state hook and useEffect to that. I'm not totally sure I understand why, but it gets rid of extra renders. And it's enough to get #3 to tolerable, although perhaps not perfect.
Here's the new code:
export function GroupingsScreen () {
... lots of stuff omitted ...
function JustTheList() {
const [groupsArray, setGroupsArray] = useState([])
useEffect(() => {
const subscriber = onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
setGroupsArray(newObject)
})
return () => subscriber();
}, [])
return(
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)
}
And then what was my return within the main functional screen component became:
return (
<JustTheList />
)
I'm still very interested in ideas for improving this code - am I missing a better way to work with RTDB and Flatlist?
im new in react-native, im passing data by navigation to my edit_note screen, once i received i set it to my states, but it doesnt work, if i print them, it shows their values, but setting to my states doesnt work, heres the code:
heres the Notes class, in the navigation function im passing the datas, data and note_number to Edit_note
render() {
return (
<>
<View style = {this.styles.View}>
<FlatList data = {this.props.data} renderItem = {({item}) => (<TouchableOpacity onPress = {() => this.props.navigation.navigate("Edit_note", {data: this.props.data, note_number: item.note_number})}><Text style = {this.styles.Text}>{item.title}</Text></TouchableOpacity>)} keyExtractor = {(item) => item.note_number.toString()}></FlatList>
</View>
</>
);
}
in Edit_note im receiving it like this:
class Edit_note extends Component {
constructor() {
super();
this.state = {
array_notes: [],
note_number: "",
}
}
componentDidMount() {
const {params} = this.props.navigation.state;
let x = params.note_number;
this.setState({note_number: x});
console.log(this.state.note_number);
}
render() {
return (
<Text></Text>
);
}
}
if i print x, it will print the note_number, but setting it into note_number, and print it, it doesnt show anything, why?
You actually set it but your console.log() fires before the change.
After a state changes component rerenders so you can try printing it on screen like;
render() {
return (
<Text>{this.state.note_number}</Text>
);
}
setState is asynchronous that means that the state indeed changes but the console.log right after the setState is not showing the change.
You can learn more about it here.
I have a database listener on my app that updates my data state every time a user posts something. When I get the new post I update my state called "posts" which is an array with all the items that the FlatList renders.
The problem is that after updating the state I can't see the new post in the top of the list until I go down (to the footer of the list) and then back to the top.
My FlatList code is:
const keyExtractor = ({id}) => id
...
const renderItem = ({ item }) => {
const {
uri,
description,
location,
width,
height,
likes,
comments,
date,
} = item;
return (
<Card
uri={uri}
description={description}
location={location}
width={width}
height={height}
likes={likes}
date={date}
/>
);
};
return (
<FlatList
data={data} // data is the post state of the parent
renderItem={renderItem}
keyExtractor={keyExtractor}
initialNumToRender={15}
windowSize={WINDOW_HEIGHT * 2}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={50}
removeClippedSubviews={false}
ListFooterComponent={
isLoading ? (
<View style={styles.footer}>
<Loading type="ios" size={18} color={colors.gray} />
</View>
) : null
}
/>
);
}
And this is how I update the post state (in the parent component of the flat list)
function Posts(props) {
const [posts, setPosts] = useState([]);
useEffect(() => {
const { firebase } = props;
let postsArray = [];
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.orderBy("date") // Sorted by upload date
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// Add the new post to the posts list
postsArray.unshift(newPost);
}
});
// Reversed order so that the last post is at the top of the list
setPosts(postsArray);
});
/* Pd: At the first time, this function will get all the user's posts */
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
return (
<View style={styles.container}>
<CardList data={posts} />
</View>
);
}
The issue could be caused by the way, you append new posts:
postsArray.unshift(newPost)
..
setPosts(postsArray)
It doesn't seem to affect the reference of postsArray, thus no state updates happen, no re-renders seen.
You may try, instead:
setPosts([...postsArray])
Assign a marker property extraData for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Your update would be :
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
extraData={/* You need to create another state variable which also changes when a post is added*/}
/>
For more information visit here
I'm having an issue getting my child component to update with new props. I'm deleting an item from the global state and it's successful. But when the item gets deleted, I'm expecting that item to no longer show. It's still being shown but if I were to move to another screen then back, it's gone. Any idea on what I might be missing here?
Thanks!!
export default class Summary extends Component {
constructor(props) {
super(props);
this.state = {
pickupData: this.props.pickup
};
}
handleDelete(item) {
this.props.deleteLocationItem(item);
}
render() {
const pickup = this.state.pickup;
return (
<View>
{pickup.map((item, i) => (
<LocationItem
name={item}
onPressDelete={() => this.handleDelete(item)}
/>
))}
</View>
);
}
}
const LocationItem = ({ onPressDelete, name }) => (
<TouchableOpacity onPress={onPressDelete}>
<Text>Hi, {name}, CLICK ME TO DELETE</Text>
</TouchableOpacity>
);
------- Additional Info ------
case 'DELETE_LOCATION_INFO':
return Object.assign({}, state, {
pickup: state.pickup.filter(item => item !== action.action)
})
export function deleteLocationInfo(x){
return {
type: DELETE_LOCATION_INFO,
action: x
}
}
Your deleteLocationItem must be something like this:
deleteLocationItem(id) {
this.setState({
items: this.state.items.filter(item => item.id !== id)
});
}
Then inside your Summary class you dont need to set the prop again. Just receive pickup from props like this:
render (
const { pickup } = this.props;
return(
<View>
{ pickup.map
...
Render is happening based on the state which is not updated other than in constructor. When the prop updates from parent, it is not reflected in the state.
Add componentWillReceiveProps method to receive new props and update state, which will cause new data to render
But more preferably, if the state is not being changed in any way after initialization, render directly using the prop itself which will resolve this issue