How to display all data - javascript

Hello I need help with showing all of a certain piece of data. my code is below
const [server, setServer] = useState()
const getServer = useCallback(async () => {
// const id = what do I do here??
const unsubscribe = firebase.db.collection("servers").doc('S7FlCYEvxIVDs7MRymnK').onSnapshot(snapshot => {
setServer(snapshot.data())
})
return unsubscribe
},[props.server])
useEffect(() => {
getServer()
}, [props])
console.log(server)
you can see I want to replace the hard coded doc info with an id variable, so in my project I can show multiple servers instead of a hard coded one I choose.

Related

Why does my react function function iterate twice?

I am currently trying out a project with the PokeAPI. And have used his guide for help. I can't get rid of the problem that the function iterates twice when called in the useEffect.
When I run the following code with the getAllPokemons in the useEffect
const PokeListPage = () => {
const [layoutToggle, setLayoutToggle] = useState(false);
const [allPokemons, setAllPokemons] = useState([]);
const [loadPoke, setLoadPoke] = useState(
"https://pokeapi.co/api/v2/pokemon?limit=20"
);
useEffect(() => {
getAllPokemons();
console.log(allPokemons);
}, []);
const getAllPokemons = async () => {
const res = await fetch(loadPoke);
const data = await res.json();
setLoadPoke(data.next);
function createPokemonObject(result) {
result.forEach(async (pokemon) => {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`
);
const data = await res.json();
setAllPokemons((currentList) => [...currentList, data]);
});
}
createPokemonObject(data.results);
console.log(allPokemons);
};
I get doublets of the first 20 objects in allPokemons. See output:
enter image description here
But when I remove the function and uses a button to trigger the function it behaves as expected. Which means that the function populates the allPokemon array with one object per pokemon. See output.
enter image description here
I have tried everything from copying entire files from other repositories and with an accuracy I didn't knew I had followed different tutorials but the problem remains. Does anyone know why?
It's bcz, you are rendering your app into a React. Strict mode component that runs specific functions and methods twice as a way to help you detect unintentional side effects. Since the side-effect is a state update, this triggers a rerender.
Use a useEffect to run the effect once when the component mounts.

Matching post author to post in React from state

I am learning React and I have successfully mapped the post titles to a div on the page. I also need to take the "userid" from the posts and get the user's name from the users state and add it to a on the div.
I can send the userid to a function which is set to return the corresponding user but it does not show up on the page. I would appreciate a review of my code to see where I am going wrong. I can log out to the console the right user but it doesn't show up on the HTML. The second in the section is the one that is not populating I would need to do that on comments as well later on so for now I just assigned a static innerHTML for that. here is my code that I have. Thanks in advance.
export default function Posts(name) {
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [users, setUsers] = useState([])
// get Posts
useEffect(() => {
getPosts()
.then(items => {
setPosts(items)
})
// Get all Users
getUsers()
.then(users => {
setUsers(users)
})
// Get all comments
getComments()
.then(comments =>{
setComments(comments)
})
}, [])
// get individual user by userId
const getPostAuthor = (userid)=> {
users.filter((item) =>{
if ( item.id === userid){
console.log(item.name)
return item.name
}
})
}
return ( <PostsSection> {
posts.map(item => <Post><PostTitle key ={ item.title }> { item.title } </PostTitle><PostBody key={item.body}>{item.body}</PostBody><PostFooter><p>comments:4</p><p>{getPostAuthor(item.userId)}</p></PostFooter></Post>)}
</PostsSection> )
}
users.filter is returning item.name, but getPostAuthor() does not return anything. You could put a return before your users.filter function but since Array.filter() returns an array and you want one user I'd expect that is not what you want. You could instead try Array.find

Function not getting all documents in a collection

I know there is a similar question that exists here but it doesn't really answer the question on here. My code currently on get the last two documents in a collection.
const [recipedata, setRecipeData] = useState([]);
const fetchRecipes = async () =>{
const response = fire.firestore().collection("recipes");
const data = await response.get();
data.docs.forEach(item=>{
setRecipeData([...recipedata, item.data()])
})
}
useEffect(() => {
fetchRecipes();
}, [])
Again when I print (recipedata) it only show two objects in the array. Can anyone tell me why this might be happening?
The query is correct and should fetch all the documents in the collection. You should check the the collection again. Also modifying the code like this should be better so you are updating the recipes array only once.
const [recipedata, setRecipeData] = useState([]);
const fetchRecipes = async () =>{
const response = fire.firestore().collection("recipes");
const data = (await response.get()).docs.map(doc => doc.data());
console.log(data)
//Updating only once
setRecipeData(data)
}
useEffect(() => {
fetchRecipes();
}, [])
Please share your collection as well so we can try replicating any issue if it has something to do with Firestore.

Reset React state before a render

I'm building a web application that consumes TMDB Api. I have the following code that fetch all information about a TV Show
export const useShowInfoFetch = ({showId}) => {
const [data, setData] = useState({})
const [loading, setLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchShowInfo = useCallback(() => {
setLoading(true)
try {
axios.get(getShowInfo(showId))
.then(response => {
setData(response.data)
})
} catch (error) {
_setError(true)
} finally {
setLoading(false)
}
}, [showId])
useEffect(() => {
fetchShowInfo()
}, [fetchShowInfo])
return [data, loading, _error]
}
All the information fetched is displayed in page, that also has Links with react-router-dom. Those links goes to another tv show page.
The problem is that when I'm in a page with a tv show that has X amount of seasons and I click a tv show with less seasons, the seasons from the page I was are persisting for a little bit of time. So, when I fetch the information for each season I got a 404 in the page that has less seasons.
Here is a screenshot of the error
The orange circle is what it's displayed since I click the tv show with less seasons.
As you can see, the seasons from the previous page are persisting for a little time, and because The Alienist has only 2 seasons (not 9) I get the 404. You can also note that latter, the correct amount of seasons are displayed.
I've tried to add a cleanup method in the useEffect hook. Something like this:
useEffect(() => {
fetchShowInfo()
return function cleanup() {
setData({})
}
}, [fetchShowInfo])
But this did not work.
I know that I can handle that with a catch after the then Axios promise, but I want to figure out why this is happening and fix the issue with a good solution instead of avoiding it.
Any help is welcomed and I can share the repository with all the code if needed.
EDIT:
To display the similar movies I use another custom hook
export const useSimilarFetch = (elementType, elementId) => {
const [similarElements, setSimilarElements] = useState({elements: []})
const [similarLoading, setSimilarLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchSimilarElements = useCallback(async (endpoint) => {
console.log(">>> fetching similar elements <<<")
setSimilarLoading(true)
try {
await axios.get(endpoint)
.then(response => {
setSimilarElements(() => ({
elements: [...response.data.results],
currentPage: response.data.page,
totalPages: response.data.total_pages
}))
})
} catch (error) {
_setError(true)
} finally {
setSimilarLoading(false)
}
}, [])
useEffect(() => {
fetchSimilarElements(getSimilar(elementType, elementId));
}, [fetchSimilarElements, elementType, elementId])
return [{similarElements, similarLoading, _error}, fetchSimilarElements]
}
Then, in my ShowInfoComponent I call all the needed hooks like this:
const {showId} = useParams()
const [data, loading, _error] = useShowInfoFetch({showId})
const [{similarElements, similarLoading}] = useSimilarFetch("tv", showId)
Thanks.
By the time showId changes, data has to wait one additional render cycle, so showId is already used even though data has not yet been fetched. The UI relies on both showId and data, yet data depends on showId. One way to solve this could be having your UI to rely on data alone. What about the id? Add it to data for example. We merely want to avoid the desynchronization.
Something like this:
export const useShowInfoFetch = ({showId}) => {
const [data, setData] = useState({})
const [loading, setLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchShowInfo = useCallback(() => {
setLoading(true)
try {
axios.get(getShowInfo(showId))
.then(response => {
setData({ id: showId, info: response.data})
})
} catch (error) {
_setError(true)
} finally {
setLoading(false)
}
}, [showId])
useEffect(() => {
fetchShowInfo()
}, [fetchShowInfo])
return [data, loading, _error]
}
Then use data.id to build your links.
If response.data already contains the id, then even better, use that.
That's just an example, of course but hopefully you get the idea.
I might be wrong but I believe you are not watching the correct value on the useEffect. You should be watching showId and not the function fetchShowInfo. That is:
useEffect(() => {
fetchShowInfo()
}, [showId]) --> HERE
And as you are memoized the callback, if you are watching the wrong variable then you will get back the 'last answered'.

Matching properties of objects in two different arrays

Background
I'm building a "Liked Stores" screen for an app I'm developing. This screen renders a flatlist which shows cards with each store a user has liked. The way I'm handling the process is that when a user likes a store, it creates a new doc in firestore which holds the id of the store and a "liked" boolean.
Problem
I've been trying to filter the totality of stores in my db and match the id's of the liked stores with the id's in the "liked" collection documents.
The data structure is:
user/uid/favorites, each doc here contains 2 fields. An id (matching to another id in the stores/ collection) and a boolean indicating that it has been liked. And then stores/'s docs contain lots of data but what matters here is a field which holds an id.
I've tried implementing the following solution, with two subscribers bringing each collection and a function which handles the filtering. But for some reason I don't quite understand I just can't get it to work.
Code is as follows:
const [stores, setStores] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('stores/')
.onSnapshot(querySnapshot => {
const stores = [];
querySnapshot.forEach(documentSnapshot => {
stores.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setStores(stores);
});
return () => subscriber();
}, [])
const [liked, setLiked] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('users/' + userId + '/favorites')
.onSnapshot(querySnapshot => {
const liked = [];
querySnapshot.forEach(documentSnapshot => {
liked.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setLiked(liked);
});
return () => subscriber();
}, [])
const usefulData = [];
const handleFilter = (obj) => {
for(let i = 0; i < stores.length; i++){
if(obj.id === liked[i].storeId) {
usefulData.push(obj)
}
}
}
stores.filter(handleFilter);
And then I'm passing usefulData as the data param to the FlatList.
When printing usefulData it simply returns an empty array.
Question
Can anyone provide a solution and an explanation as to why this is not working and how to compare two arrays of objects' values properly?
Well, figured it out in the most uncomfortable and least elegant possible way so any tips or better ideas will still be fondly accepted:
const likedIds = [];
liked.forEach(o => likedIds.push(o.id));
const storeIds = [];
stores.forEach(o => storeIds.push(o.id));
const intersection = storeIds.filter(element => likedIds.includes(element));
const [data, setData] = useState([]);
const finalArray = [];
useEffect(() => {
for (let i = 0; i < intersection.length; i++){
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(intersection[i])
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
finalArray.push(documentSnapshot.data())
}
});
setData(finalArray)
}
fetchQuery()
}
}, [])
Without changing anything else from what is posed in the question, this is what I added in order to get this working. First I get all the Id's out of each array of objects, then filter them and store them in a new array. Then we can go ahead and build a new array of objects using a handy fetchQuery async function to get all the data from the original stores collection, just that this time it only brings up the stores with ids in the intersection array.
I am perfectly aware that this is the most bodged out and disgusting possible solution, but at the very least it works. Hope someone smarter than me can get some help out of this and make my solution much better!

Categories

Resources