React doesn't re-render my component when the array is changed? - reactjs

I've set different options for sorting various listings, and i've set an empty array for my listings. using firebase for backend, so basically everything is working, but the data isn't displayed after useEffect does it's work. as soon as I change the sort option the listings appear. after refreshing it is the same again.
I'm passing the listings and sort as props to another child component which is a grid layout. Everytime I've to manually change the sort so see data, how can it be so that it automatically show's the data according to the sort option.
const [sort, setSort] = useState("option1");
const [listings, updateListings] = useState([]);
const db = getFirestore();
const colRef = collection(db, "Listing");
useEffect(() => {
let defaultListings = [];
getDocs(colRef)
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
defaultListings.push({ ...doc.data(), id: doc.id });
});
})
.catch((err) => {
console.log(err.message);
});
updateListings(defaultListings);
}, []);
return(<Listing list = {listings} sortoption={sort} />)
this listing component shows the grid
i've tried adding dependencies, but it results in an infinite loop, while without any dependencies the component doesn't show anything.

You need to call updateListings inside then, otherwise it will run before you modify the defaultListings array.
So:
getDocs(colRef)
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
defaultListings.push({ ...doc.data(), id: doc.id });
});
updateListings(defaultListings); // 👈
})
.catch((err) => {
console.log(err.message);
});

Related

react useEffect strange behaviour - can't explain?

I have the following code in a useEffect
useEffect(() => {
async function fetchMessages() {
let messages = [];
const firestore = firebase.firestore();
const query = firestore.collection('chats').where("repliedTo", "==", false).where("type", "==", "StudentQuery").orderBy("timestamp", "desc");
query.onSnapshot({
next: (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log("x ", doc.id, '=>', doc.data());
messages.push({mid: doc.id, ...doc.data()});
console.log(messages)
});
},
});
setMessagesList(messages)
setMessageCount(messagesList.length)
console.log('xxxx' + messagesList.length)
}
fetchMessages();
}, [messagesList.length]);
A few things seem to be wrong with this and I can't see it.
When I trigger this code (by inserting a new record into Firestore) what I expect would be to see a console.log with the (final) array size (so previous array size + 1) - but instead what I am seeing is the previous array + (previous array + 1 entry) I would have thought he let messages = [] would have cleared the array every time an update happened?
I never see the console.log("xxx") in my console. I want to put a state update here as this line should be safe as the database read has done, but since the line doesn't appear I don't know what's going wrong.
Can anyone shed some insight?
I've not used firebase before but it looks like you're effectively creating a subscription which is getting called outside of React's render cycle.
You could just add a state property that you update when next is called, eg:
const [messages, setMessages] = useState([]);
// Use the `useEffect` to set up / tear down the subscription
useEffect(() => {
const firestore = firebase.firestore();
const query = firestore.collection(...);
const unsubscribe = query.onSnapshot({
next: (querySnapshot) => {
setMessages(prev => [
...prev,
...querySnapshot.map(doc => ({
mid: doc.id,
...doc.data(),
})),
]);
});
});
// Unsubscribe when you unmount
return () => unsubscribe();
}, [])

Updating the state correctly after fetch data from firestore

I am trying to use Firestore Snapchats to get real time changes on a database. I am using react-native-cli: 2.0.1 and react-native: 0.64.1 .
export const WelcomeScreen = observer(function WelcomeScreen() {
const [listData, setListData] = useState([]);
const onResult = (querySnapshot) => {
const items = []
firestore()
.collection("Test")
.get()
.then((querySnapshot) => {
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data}
items.push(tempData);
});
setListData(items)
});
}
const onError = (error) => {
console.error(error);
}
firestore().collection('Test').onSnapshot(onResult, onError);
}
Every thing is working perfectly, until I use setListData to update the data list. The App does not respond anymore and I get a warning message each time I try to add or delete data from the database
Please report: Excessive number of pending callbacks: 501. Some pending callbacks that might have leaked by never being called from native code
I am creating a deadlock by setting the state this way?
First, you don't want to set up a snapshot listener in the body of your component. This results in a growing number of listeners, because every time you render you add a new listener, but every listener results in rendering again, etc. So set up the listener just once in a useEffect:
const [listData, setListData] = useState([]);
useEffect(() => {
function onResult(querySnapshot) {
// ...
}
function onError(error) {
console.log(error);
}
const unsubscribe = firestore().collection('Test').onSnapshot(onResult, onError);
return unsubscribe;
}, []);
In addition, your onResult function is going to get called when you get the result, and yet you're having it turn around and immediately doing a get to re-request the data it already has. Instead, just use the snapshot you're given:
function onResult(querySnapshot) {
const items = []
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data()}
items.push(tempData);
});
setListData(items);
}

How to stop useEffect from reloading my page every time?

For some reason my whole page reloads every time it updates the state after it gets it from the database. The page flickers and I end up at the top of the page. Why is this?
I update the entire state in other functions like sort(), that works perfect without reloading. I have put event.preventDefault() in every click handler so that shouldn't be the problem.
One of the great things with using React is to have a smooth UI without reloading so this is annoying.
function App() {
const [contacts, setContacts] = useState({ items: [] });
useEffect(() => {
axios
.get('http://localhost:5000/')
.then((result) => {
setContacts({ items: result.data });
})
.catch((err) => console.log(err));
}, []);
And this is the function that gets called:
const handleSubmit = (event) => {
event.preventDefault();
if (!id) {
axios
.post('http://localhost:5000/add/', input)
.then(() => {
setInput(emptyState);
})
.catch((err) => console.log(err));
} else {
axios
.post(`http://localhost:5000/update/${id}`, input)
.then(() => {
props.updateContact(input);
setInput(emptyState);
})
.catch((err) => console.log(err));
}
window.location = '/';
};
You need to put something in your [].
You can see that we passed props.name into the array in the second argument. This will now cause the effect to always run again when the name changes.
If you don't pass anything it will always update and will be useless.
useEffect(() => {
document.title = `Page of ${props.name}`
}, [props.name])

value of state is always default. React js 16.12.0

I have two useEffect-s. One is used to fetch data from api and save it in the state and second is called only once and it starts listening to websocket event.
In the websocket event handler I log the fetched data but it always has the default value.
Even though fetching data completes successfully and the list is drawn on UI, the value of list is always empty - [].
const [list, setList] = useState([]);
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then(res => {
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
});
}, []);
Your second effect is referencing the initial list value (an empty array) due to closure. This is why useEffect should reference all of its dependencies in its second argument.
But in this case, where you don't want to subscribe to the webSocket event each time the list is updated, you could use React's refs on the list.
const listValue = useRef([]);
const [list, setList] = useState(listValue.current);
When setting the value:
res => {
listValue.current = res.data
setList(listValue.current);
}
And when retrieving the list in a one time fired useEffect:
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(listValue.current);
});
}, []);
try changing
.then(res => {
to
.then((res) => {
Would clarify if you added console logs to each hook or said if the values are preset in them:
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then((res) => {
console.log(res.data)
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
console.log(message);
});
}, []);
You could also add error catch, just in case:
.catch((error) => {
console.log(error.response)
})

I am getting data back but the array length is returned as 0

I am trying to grab data from firebase and it console logs correctly but array says length is 0
useEffect(() => {
let items = [];
const unsubscribe = store
.collection('users')
.doc(user.uid)
.collection('redirects')
.onSnapshot(snapShot => {
snapShot.forEach(getPath => {
const { path } = getPath.data();
store.doc(path).onSnapshot(doc => {
const data = doc.data();
items.push({ ...data });
});
});
});
setDocs(items);
setIsLoading(false);
return () => unsubscribe();
}, []);
console.log(docs);
Try something like this:
Your setState call should be inside the onSnapShot(()=>{}) callback, because the it's asynchronous. The way you're doing, you're basically trying to call setDocs() with an empty items array.
useEffect(() => {
let items = [];
const unsubscribe = store
.collection('users')
.doc(user.uid)
.collection('redirects')
.onSnapshot(snapShot => {
snapShot.forEach(getPath => {
const { path } = getPath.data();
store.doc(path).onSnapshot(doc => {
const data = doc.data();
items.push({ ...data });
});
});
setDocs(items);
setIsLoading(false);
});
//setDocs(items);
//setIsLoading(false);
return () => unsubscribe();
}, []);
As others have mentioned, it seems to be a async issue.
The console in chrome will show resolved object values, not necessarily what they were at run time.
To show you what i mean, change your console log from console.log(docs) to console.log(JSON.stringify(docs)) and it will show you the string value of docs at run time which should be an empty array, which would make the length 0 make sense (because it is 0 until the async call is resolved).

Resources