Rerendering a component - ReactJS, Axios - reactjs

After calling canBookSlot I want to update the slotsList I figure i have to make a new Axios request, can i reuse the useEffect whitin the then() method to rerender the component with updated properties or is there any other smart way of doing it without rewriting the Axios request?
useEffect(() => {
Axios.post("http://localhost:3001/api/get/week1/ex").then((response) => {
setSlotsList(response.data);
});
}, []);
let userDetailsString = localStorage.getItem("userDetails");
const userDetailsObj = JSON.parse(userDetailsString);
const canBookSlot = (id) => {
if (userDetailsObj.canBook != 0) {
Axios.post("http://localhost:3001/api/book/week1/ex", {
room: userDetailsObj.room,
id: id.id + 1,
}).then(); // update the slotsList
}
};
EDIT:
The userDetailsObj is an object from another component, It isn't the same object as the ones in slotList how do i go about rerendering userDetailsObj
const updateData = () => {
Axios.post("http://localhost:3001/api/get/week1/ex").then((response) => {
setSlotsList(response.data);
});
}
useEffect(() => {
updateData();
}, []);
let userDetailsString = localStorage.getItem("userDetails");
let userDetailsObj = JSON.parse(userDetailsString);
const canBookSlot = (id) => {
if (userDetailsObj.canBook != 0) { // Always true
Axios.post("http://localhost:3001/api/book/week1/ex", {
room: userDetailsObj.room,
id: id.id + 1,
}).then(() => updateData())
}
};

You can create common function and reuse when you want to call that axios api and update the data.
updateData = () => {
Axios.post("http://localhost:3001/api/get/week1/ex").then((response)
=> {
setSlotsList(response.data);
});
}
useEffect(() => {
updatedData();
}, []);
let userDetailsString = localStorage.getItem("userDetails");
const userDetailsObj = JSON.parse(userDetailsString);
const canBookSlot = (id) => {
if (userDetailsObj.canBook != 0) {
Axios.post("http://localhost:3001/api/book/week1/ex", {
room: userDetailsObj.room,
id: id.id + 1,
}).then(() => updateData()); // update the slotsList
}
};

Related

update React state after fetching referenced document

I have a simple React App using Firestore.
I have a document in Firestore:
{
date: November 20, 2022 at 11:24:44 AM UTC+1,
description: "...",
title: "Dummy title",
type: "student",
userRef: /users/0AjB4yFFcIS6VMQMi7rUnF3eJXk2
}
Now I have a custom hook, that fetches the data:
export const useAnnouncements = () => {
const [announcements, setAnnouncements] = useState([]);
useEffect(() => {
getAnnouncements().then((documents) => {
const documentsList = [];
documents.forEach((doc) => {
const document = { id: doc.id, ...doc.data() };
getUser(document.userRef).then((u) => {
document.user = u.data(); // <-- HERE is problem
});
documentsList.push(document);
setAnnouncements(documentsList);
});
});
}, []);
return [announcements];
};
Problem is that I have a REFERENCE field type, and it has to be fetched separately. Result? My list is populated, but first without user. Later, when the users' data is fetched, the state is not being updated.
How to deal with React + Firestore's reference field?
Array.prototype.forEach is not designed for asynchronous code. (It was not suitable for promises, and it is not suitable for async-await.) instead you can use map.
useEffect(() => {
getAnnouncements().then((documents) => {
const promises = documents.map((doc) => {
return getUser(doc.userRef).then((u) => {
const document = { id: doc.id, user: u.data(), ...doc.data() };
return document;
});
});
Promise.all(promises).then((documentsList) => {
setAnnouncements(documentsList);
});
});
}, []);
I think you need to wait for all the data to be fetched
export const useAnnouncements = () => {
const [announcements, setAnnouncements] = useState([]);
useEffect(() => {
let isValidScope = true;
const fetchData = async () => {
const documents = await getAnnouncements();
if (!isValidScope) { return; }
const allPromises = documents?.map(doc => {
return getUser(doc.userRef)
.then(user => {
return {
id: doc.id,
...doc.data(),
user: user.data()
}
})
}
const documentsList = await Promise.all(allPromises);
if (!isValidScope) { return; }
setAnnouncements(documentsList);
}
fetchData()
return () => { isValidScope = false }
}, []);
return [announcements];
};
Hope it helps in some way

react/ state not being updated because of cleanup

I have custom hook that adds user info for posts created. And if I don't add cleanup it works as intended. I create new post, press post and it gets added to screen, but with if(mounted.current)setState() it does not update, only on refresh. What could be the problem and how could I fix it?
const AllPostsAssign = () => {
const { userPosts, allUsers } = useData();
const [posts, setPosts] = useState();
const mounted = useRef(true);
// map existing posts and add user object and post id into post object.
useEffect(() => {
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted.current) setPosts(noUndefined);
console.log(mounted.current);
console.log(posts);
return () => (mounted.current = false);
}, [userPosts, allUsers]);
// sort by time created and then reverse, so newest posts would be on top.
posts &&
posts.sort((a, b) => {
return a.createdAt.seconds - b.createdAt.seconds;
});
posts && posts.reverse();
return posts;
};
export default AllPostsAssign;
Have your mounted check declared directly inside your useEffect, as such:
useEffect(() => {
let mounted = true;
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted) setPosts(noUndefined);
console.log(mounted);
console.log(posts);
return () => (mounted = false);
}, [userPosts, allUsers]);

Re-Render all values from DB or add to displayed the new one (React)

I have displayed values from Firebase and when user is adding new value I want to re-render all display values (or add just new).
const addProfile = async (e) => {
e.preventDefault();
const newProfile = {
x: x,
y: y
}
await profile.add({ newProfile })
displayProfileList();
}
const displayProfileList = async () => {
await profile.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const nProfile = {doc: doc.data() }
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
}
useEffect(() => {
displayProfileList();
}, []);
But to actual list is adding again all values from DB- So how can I first clear displayed data? or add to currently displayed data only new value?
The best way to do it is by not calling displayProfileList() from inside addProfile method. Rather, after the firebase update is done, just call setAllProfile([...allProfile, newProfile]). This will update the list with just the new profile and render it again.
const addProfile = async (e) => {
e.preventDefault();
const newProfile = {
x: x,
y: y
}
await profile.add({ newProfile })
setAllProfile([...allProfile, newProfile]);
}
const displayProfileList = async () => {
await profile.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const nProfile = {doc: doc.data() }
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
}
useEffect(() => {
displayProfileList();
}, []);

How to filter out data that is set to a state in react

I'm trying to build a search function that displays items that match the searchTerm. The data I am getting is from an API, I want filter out all items apart from the searchedTerm item, the initial API call runs once, with useEffect and [] callback
const changeFilterItem = (values) => {
const data = [...item];
const index = data.indexOf(values);
if (index > -1) {
data.splice(index, 1);
} else {
data.push(values);
}
setItem([...data]);
};
useEffect(() => {
if (item !== null) {
setLoading(true);
let pokemonList = [];
async function fetchData() {
for (let i = 0; i < item.length; i++) {
let response = await getAllPokemonByType(initialURLType, item[i]);
const pokemons = [...response.pokemon, ...pokemonList];
pokemonList = pokemons.slice(0);
}
console.log(pokemonList);
await loadPokemonByFilter(pokemonList);
}
fetchData().then();
}
}, [item]);
const loadPokemonByFilter = async (data) => {
let _pokemonData = await Promise.all(
data.map(async (pokemon) => {
return await getPokemon(pokemon.pokemon);
})
);
setPokemonData(_pokemonData);
setLoading(false);
};
const renderSelected = (type) => {
if (item.indexOf(type) === -1) {
return "";
}
return classes.selected;
};
What i've understood so far is that you want to filter out data from an array that has some data. For that you can use matchSorter.
Here is the link for its docs:
https://github.com/kentcdodds/match-sorter
const [master, setMaster] = React.useState([]);
const [filtered, setFiltered] = React.useState([]);
React.useEffect(() => {
makeApiCall().then(response => {
setMaster(response);
setFiltered(response);
});
}, []);
function filterData() {
setFiltered(master.filter(n => n.name === 'valueFromTextInput' && n.age === 'valueFromTextInput'));
}
Use filtered to your data grid where you want to show data. Call filterData function from your function where you want to do filtering.
Hope it helps.

React Hooks: best practice to get user authenticated

I am changing a React app from class based to function based. In the based class the declaration of listeners are in the lifecycle method componentDidMount():
componentDidMount() {
this.getNotes()
Auth.currentAuthenticatedUser().then(user => {
this.setState({user: user});
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner:this.state.user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote]
this.setState({ notes: updatedNotes })
}
})...
To unsubscribe the listener I use the lifecycle method:
componentWillUnmount(){
this.createNoteListener.unsubscribe()
Changing to a function based class the listener declaration is like this:
useEffect(() => {
getNotes()
Auth.currentAuthenticatedUser().then(user => {
const createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
............
return () => {
createNoteListener.unsubscribe() //the error is here
}
I am getting an erro saying: 'createNoteListener' is not defined.
Since I need the authenticated user to create the listener, how/where should I get/set the user before declaring the listener?
Thank you all!
createNoteListener is defined in different scope.
Can you try this?
useEffect(() => {
getNotes()
let createNoteListener = null;
Auth.currentAuthenticatedUser().then(user => {
createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
}
)}
)
return () => {
createNoteListener.unsubscribe() //the error is here
}
}
)
I think you need to provide an extra argument to the useEffect Hook
You can do this
useEffect(() => {
getNotes()
Auth.currentAuthenticatedUser().then(user => {
const createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
............
return () => {
createNoteListener.unsubscribe()
}, []) // provide empty array as second argument
This is because you want to subscribe only once.
Hope it helps

Resources