state doesn't clear in useEffect - reactjs

I wanted to re-render my component and update array of events when filters are changing.
const filters = useSelector(state => state.mapRedux.filters)
const [allEvents, setAllEvents] = useState([]);
const getAllEvents = async (start) => {
let myEventsArray = [];
await firebase.firestore().collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy("data_rozpoczecia")
.startAfter(start)
.limit(limit)
.get().then(snapshot => {
if (snapshot.size < limit) setShowMore(false)
snapshot.forEach(doc => {
let info = doc.data()
let el = {
id: doc.id,
...info
}
myEventsArray.push(el)
});
})
let new_array = allEvents.concat(myEventsArray)
setAllEvents(new_array);
}
useEffect(() => {
setAllEvents([])
getAllEvents(new Date());
}, [filters])
And that works, but I don't why setAllEvents([]) doesn't clear my events array. Instead new array is joins with old one and I get duplicate of some elements.

Here is what you can do to prevent stale closures but not run the effect too many times:
const AllEvents = (props) => {
const currentCity = useSelector(
(state) => state.mapRedux.city.name
);
const [allEvents, setAllEvents] = useState([]);
const [limit, setLimit] = useState(6);
const [showMore, setShowMore] = useState(true);
// filtry
const filters = useSelector(
(state) => state.mapRedux.filters
);
const createFiltersTable = React.useCallback(() => {
const tmp = Object.values(filters);
const values = [];
tmp.map((el) => {
if (el.active) values.push(el.events_name);
});
return values;
}, [filters]); //re create this function when filters change
const getAllEvents = React.useCallback(
async (start) => {
let myEventsArray = [];
await firebase
.firestore()
.collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy('data_rozpoczecia')
.startAfter(start)
.limit(limit)
.get()
.then((snapshot) => {
if (snapshot.size < limit) setShowMore(false);
snapshot.forEach((doc) => {
let info = doc.data();
let el = {
id: doc.id,
...info,
};
myEventsArray.push(el);
});
});
setAllEvents((allEvents) =>
//use callback to prevent allEvents being a dependency
allEvents.concat(myEventsArray)
);
},
//re create getAllEvents when createFiltersTable, currentCity
// or limit changes
[createFiltersTable, currentCity, limit]
);
useEffect(() => {
setAllEvents([]);
getAllEvents(new Date());
//effect will run when filters change or when
// getAllEvents change, getAllEvents will change
// when filters, currentCity or limit changes
}, [filters, getAllEvents]);
return ...;
};

Related

how to auto delete data after new data has been uploaded on firebase on react?

When a user updates their profile photo I want old data to be overwritten on firestore. I'm using firebase storage to store photos and upload firebase URL to restore database so
I tried filtering in on the front end side but I have multiple users to filter and there are a lot of duplicates
here is whole functionality of uploading data to firestore storage then updating firestore db and then pulling data with use
const [userImg, setUserImg] = useState()
const [image, setImage] = useState(null)
const [htlmImg, setHtmlImg] = useState(null)
const [url, setUrl] = useState(null)
const [userName, setUserName] = useState(null)
const [sureLoading, setSureLoading] = useState(false)
const [photoEdit, setPhotoEdit] = useState(false)
const handleImageChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0])
setHtmlImg(URL.createObjectURL(e.target.files[0]))
}
}
const uploadImg = () => {
const imageRef = ref(storage, `image${user.uid}`)
uploadBytes(imageRef, image)
.then(() => {
getDownloadURL(imageRef)
.then((url) => {
setUrl(url)
})
.catch((error) => {
console.log(error.message, 'error getting the image url')
})
setImage(null)
})
.catch((error) => {
console.log(error.message)
})
setSureLoading(true)
}
const handlePfpSubmit = async () => {
const { uid } = user
if (url !== null) {
try {
await addDoc(collection(db, 'user'), {
pfp: url,
userName,
uid,
timestamp: serverTimestamp(),
time: Date(),
})
if (!photoEdit) {
navigate('/test')
}
console.log('data send')
} catch (err) {
console.log(err)
}
}
}
const [displayName, setDisplayName] = useState(null)
const [displayPhoto, setDisplayPhoto] = useState(null)
const [userProfiles, setUserProfiles] = useState(null)
useEffect(() => {
const q = query(collection(db, 'user'),
orderBy('timestamp')).update()
const unsub = onSnapshot(q, (querySnapShot) => {
let photo = []
querySnapShot.forEach((doc) => {
photo.push({ ...doc.data(), id: doc.id })
})
console.log(photo)
console.log('data resived')
let userUid = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { pfp } = item
return pfp
})
setDisplayPhoto(
userUid.filter((val, index) => {
if (userUid.length - 1 <= index) {
return val
}
}),
)
let userUidName = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { userName } = item
return userName
})
let photoFilter = userUidName.filter((val, index) => {
if (userUidName.length - 1 <= index) {
return val
}
})
setDisplayName(photoFilter)
setUserProfiles(photo)
console.log(displayPhoto)
})
console.log('re render ? ')
return () => unsub()
}, [user])

Getting a undefined value when trying to match fetch results to people objects

Im working on a star wars api app. I am getting an array of people objects, 10 characters. Who all are their own object with different values. However homeworld, and species are urls. So I have to fetch them and store that data to the correct place. I figured out a way to get the homeworld values to each character. However when I try to do it with species I receive undefined. Would appreciate any help this has been kind of a pain thanks ahead of time !
const [people, setPeople] = useState([]);
const [homeWorld, setHomeWorld] = useState([]);
const [species, setSpecies] = useState([]);
const [nextPageUrl, setNextPageUrl] = useState("https://swapi.dev/api/people/");
const [backPageUrl, setBackPageUrl] = useState('');
const [test, setTest] = useState([]);
const fetchPeople = async () => {
const { data } = await axios.get(nextPageUrl);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
return data.results;
}
const backPage = async () => {
const { data } = await axios.get(backPageUrl);
setCharacters(data.results);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
}
// Get People
async function getPeople() {
const persons = await fetchPeople();
const homeWorldUrl= await Promise.all(
persons.map((thing) => axios.get(thing.homeworld)),
);
const newPersons = persons.map((person) => {
return {
...person,
homeworld: homeWorldUrl.find((url) => url.config.url === person.homeworld)
};
});
const newPersons2 = newPersons.map((person) => {
return {
...person,
homeWorld: person.homeworld.data.name
};
});
setPeople(newPersons2);
}
// Get Species
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons.map((thing) => axios.get(thing.species)),
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species)
};
});
setTest(newSwapi);
// const newPersons2 = newPersons.map((person) => {
// return {
// ...person,
// homeWorld: person.homeworld.data.name
// };
// });
}
useEffect(() => {
getPeople();
getSpecies();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); ```
Species property of person is a array, so your getSpecies() should be like
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons
.filter((thing) => thing.species.length)
.map((thing) => axios.get(thing.species[0]))
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species[0])
};
});
setTest(newSwapi);
}

Not getting currentUserID from firebase immediately

I'm trying to access a single user data from firebase by passing currentUserId to useCollection hook but currentUser?.id is not available when the application first loads.
this is how I calling my hooks from a component.
const { currentUser } = useAuth()
const { docs = [] } = useCollection("solutions", currentUser?.id, false)
useCollection hook:
const useCollection = (collection, _userID, _openTab) => {
const [docs, setDocs] = useState([])
const [loading, setLoading] = useState(true)
const userID = useRef(_userID).current
const openTab = useRef(_openTab).current
// getting realtime data from the firebase for challenges and solutions
useEffect(() => {
let ref = firestore.collection(collection)
console.log(_userID)
if (openTab && userID) {
console.log("open")
openTab === 1
? (ref = ref.where("userID", "==", userID).where("completed", "==", false))
: (ref = ref.where("userID", "==", userID).where("completed", "==", true))
}
if (userID) {
ref = ref.where("userID", "==", userID)
console.log("first")
}
const unsubscribe = ref.onSnapshot(
(snapshot) => {
const results = []
snapshot.docs.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id })
})
// update state
setDocs(results)
setLoading(false)
},
(error) => {
console.log(error)
}
)
// unsubscribe on unmount
return () => unsubscribe()
}, [collection, openTab, loading]) // eslint-disable-line react-hooks/exhaustive-deps
return { docs, loading }
}
Anyone please help me with this!

React and Firebase Firestore V9 Previous Page Pagination returning to "first page"

I'm at a loss here. I feel like I've been trying everything, and using the exact methods explained in other posts/tutorials everywhere. I understand that you need to use a cursor and set the first and last visible document so that you can start after the last, in the case of moving forward, and start BEFORE the first, in the case of moving backwards.
In my implementation, going forwards works fine. However, when I utilize the previousPage function, it returns me to the first page, despite setting the 'first visible' document. It returns to the first page even if I've already moved 3 'pages' forward.
Clearly there is something I'm not understanding here..
const PAGE_SIZE = 6;
const [posts, setPosts] = useState([]);
const [lastVisible, setLastVisible] = useState(null);
const [firstVisible, setFirstVisible] = useState(null);
const [loading, setLoading] = useState(false);
// Initial read to get first set of posts.
useEffect(() => {
const q = query(
collectionGroup(db, "bulletins"),
orderBy("createdAt", "desc"),
limit(PAGE_SIZE)
);
const unsubscribe = onSnapshot(q, (documents) => {
const tempPosts = [];
documents.forEach((document) => {
tempPosts.push({
id: document.id,
...document.data(),
});
});
setPosts(tempPosts);
setLastVisible(documents.docs[documents.docs.length - 1]);
setFirstVisible(documents.docs[0]);
});
return () => unsubscribe();
}, []);
const nextPage = async () => {
const postsRef = collectionGroup(db, "bulletins");
const q = query(
postsRef,
orderBy("createdAt", "desc"),
startAfter(lastVisible),
limit(PAGE_SIZE)
);
const documents = await getDocs(q);
updateState(documents);
};
const previousPage = async () => {
const postsRef = collectionGroup(db, "bulletins");
const q = query(
postsRef,
orderBy("createdAt", "desc"),
endBefore(firstVisible),
limit(PAGE_SIZE)
);
const documents = await getDocs(q);
updateState(documents);
};
const updateState = (documents) => {
if (!documents.empty) {
const tempPosts = [];
documents.forEach((document) => {
tempPosts.push({
id: document.id,
...document.data(),
});
});
setPosts(tempPosts);
}
if (documents?.docs[0]) {
setFirstVisible(documents.docs[0]);
}
if (documents?.docs[documents.docs.length - 1]) {
setLastVisible(documents.docs[documents.docs.length - 1]);
}
};
You should use endAt() instead of endBefore() and also, you should pass the order reference which is the createdAt to the endAt() method. See code below:
const PAGE_SIZE = 6;
const [posts, setPosts] = useState([]);
const [lastVisible, setLastVisible] = useState(null);
const [firstVisible, setFirstVisible] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const q = query(
collectionGroup(db, "bulletins"),
orderBy("createdAt", "desc"),
limit(PAGE_SIZE)
);
const unsubscribe = onSnapshot(q, (documents) => {
const tempPosts = [];
documents.forEach((document) => {
tempPosts.push({
id: document.id,
...document.data(),
});
});
setPosts(tempPosts);
setLastVisible(documents.docs[documents.docs.length - 1]);
setFirstVisible(documents.docs[0]);
});
return () => unsubscribe();
}, []);
const nextPage = async () => {
const postsRef = collectionGroup(db, "bulletins");
const q = query(
postsRef,
orderBy("createdAt", "desc"),
startAfter(lastVisible.data().createdAt), // Pass the reference
limit(PAGE_SIZE)
);
const documents = await getDocs(q);
updateState(documents);
};
const previousPage = async () => {
const postsRef = collection(db, "bulletins");
const q = query(
postsRef,
orderBy("createdAt", "desc"),
endAt(firstVisible.data().createdAt), // Use `endAt()` method and pass the reference
limitToLast(PAGE_SIZE)
);
const documents = await getDocs(q);
updateState(documents);
};
const updateState = (documents) => {
if (!documents.empty) {
const tempPosts = [];
documents.forEach((document) => {
tempPosts.push({
id: document.id,
...document.data(),
});
});
setPosts(tempPosts);
}
if (documents?.docs[0]) {
setFirstVisible(documents.docs[0]);
}
if (documents?.docs[documents.docs.length - 1]) {
setLastVisible(documents.docs[documents.docs.length - 1]);
}
};
For more information, See Add a simple cursor to a query.

How to get the current state inside socket.io on callback function

const useChat = () => {
const [messages, setMessages] = useState([]);
const socketRef = useRef();
const { chatId } = useSelector(state => state.chatin)
const { chatList } = useSelector(state => state.chatin)
const dispatch = useDispatch()
useEffect(() => {
socketRef.current = io(socketClient);
socketClient.on('chat', (data) => {
const targetMessage = (messages) => messages.findIndex(item => item.message_number === data.message_number);
console.log('targetMessage', targetMessage)
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
setMessages((messages) => [...messages, data]);
});
return () => {
socketRef.current.disconnect();
};
}, []);
whenever I got new socket data, I wanna change 'messages' data, but can't access it, because it always shows initial data value.After that I have a question about how can I set it?
You can move the if condition inside setMessages function, this way you will get access to the current state:
socketClient.on('chat', (data) => {
setMessages((messages) => {
const targetMessage = messages.findIndex(item => item.message_number === data.message_number);
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
return [...messages, data]
});
});

Resources