Mapping into firestore from React - reactjs

I am new to react and firebase/firestore.
I am trying to map into what I believe to be a nested firestore value. I am able to pull each value individually
function Pull() {
const [blogs,setBlogs]=useState([])
const fetchBlogs=async()=>{
const response=firestore.collection('customer');
const data= await response.get();
data.docs.forEach(item=>{
setBlogs(data.docs.map(d => d.data()))
console.log(data)
})
}
useEffect(() => {
fetchBlogs();
}, [])
return (
<div className="App">
{
blogs.map((items)=>(
<div>
<p>{items[1].name}</p>
</div>
))
}
</div>
);
}
I have been trying to map twice to get into the string inside the collection, yet I have had no luck.
My FireStore collection
https://drive.google.com/file/d/1Erfi2CVrBSbWocQXGR5PB_ozgg9KEu12/view?usp=sharing
Thank you for your time!

If you are iterating a data.docs array and enqueueing multiple state updates then you will want to use a functional state update to correctly enqueue, and update from the previous state.
const fetchBlogs = async ( )=> {
const response = firestore.collection('customer');
const data = await response.get();
data.docs.forEach(item => {
setBlogs(blogs => blogs.concat(item.data()))
});
}
or you can map the data.docs to an array of items and update state once.
const fetchBlogs = async ( )=> {
const response = firestore.collection('customer');
const data = await response.get();
setBlogs(blogs => blogs.concat(data.docs.map(item => item.data())));
}

try changing the foreach to a snapshot like this:
data.docs.onSnapshot(snapshot=>{
setBlogs(snapshot.docs.map(d => d.data()))
console.log(data)
})
ive used it like this in the past multiple times
and it has worked. If it doesnt work, the instagram clone tutorial on youtube by Clever Programmer goes over this well.

Related

How to display an array in JSX from a function in React?

I implemented a function where I fetch all Docs from a Firebase collection on a click.
Now I want to display each doc I fetched in a <div> container in JSX. When I try to take the array and display it, I´m getting the error that the array is not found.
This is my code:
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"))
const allDivs = [];
querySnapshot.forEach(doc => {
allDivs.push(doc.data().DivContent);
});
}
You would have to return the array from the function, because of the "scope".
Example:
//your current function
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"));
return querySnapshot.map((doc) => doc.data().DivContent);
}
//your component
let divs = getAllDivs(); //you can now use "divs" in this scope
return (
<>
divs.map((current_div) => { <div>{current_div}</div> })
</>
)
Also, I suggest against pushing data to an array labeled as const, as it could be confusing for someone else reading your code.
I think you could use something like this:
const MyComponent = () => {
const [docs, setDocs] = useState();
const onClickHandler = async () => {
const docs = await getDocs(collection(db, "Div"));
setDocs(docs);
}
return (
<>
<button onClick={onClickHandler}>Get docs</button>
{docs && docs.map(doc => (
<div>{doc.data().DivContent}</div>
))}
</>
)
}
If DivContent contains HTML you can use dangerouslySetInnerHTML.

React array is empty after I filled it in Firebase function

I'm trying to load data using React and Firebase.
Unfortunately, I can't get it to display them.
In Firebase's res.items.forEach((itemRef) function, I fill an array.
I would like to access this later. However, this is then empty. How so ?
const array = [];
function loadList() {
const storage = getStorage();
const storageRef = sRef(storage, "images/");
// Find all the prefixes and items.
listAll(storageRef)
.then((res) => {
res.prefixes.forEach((folderRef) => {
// All the prefixes under listRef.
// You may call listAll() recursively on them.
});
res.items.forEach((itemRef) => {
array.push(itemRef._location);
// All the items under listRef.
console.log(itemRef._location);
});
})
.catch((error) => {
// Uh-oh, an error occurred!
});
}
function App() {
loadList();
return (
<div className="App">
<Stack direction="row" alignItems="center" spacing={2}>
// ARRAY IS EMPTY
{array?.map((value, key) => {
return <h1>{value}</h1>;
})}
...
From your explaination, here is what I gathered you are trying to do:
Fetch the documents from cloud firestore.
Store them in an array.
Create a list of Components in the browser view rendered by react using the array that was fetched.
For such, fetching and rendering tasks, your best bet would be using Callbacks, State and Effect.
So:
State would hold the values for react to render.
Effect will fetch the data when the component loads.
Callback will do the actual fetching because asynchronous fetching on useEffect directly is discouraged.
const storage = getStorage();
const listRef = ref(storage, 'files/uid');
function ImageApp()
{
// This is the array state of items that react will eventually render
// It is set to empty initially because we will have to fetch the data
const [items, setItems] = React.useState([]);
React.useEffect(() =>
{
fetchItemsFromFirebase();
}, []); // <- Empty array means this will only run when ImageApp is intially rendered, similar to onComponentMount for class
const fetchItemsFromFirebase = React.useCallback(async () =>
{
await listAll(listRef)
.then((res) =>
{
// If (res.items) is already an array, then use the method below, it would be faster than iterating over every single location
// cosnt values = res.items.map((item) => item._location);
// If res.items doesnt already return an array, then you unfortunately have to add each item individually
const values = [];
for (const itemRef of res.items)
{
values.push(itemRef._location);
}
console.log(values);
setItems(values);
})
.catch((error) => { console.error(error) });
}, []); // <- add "useState" values inside the array if you want the fetch to happen every smth it changes
return (
<div className="App">
{
items && // Makes sure the fragment below will only be rendered if `items` is not undefined
// An empty array is not undefined so this will always return false, but its good to use for the future :)
<>
{
(items.map((item, itemIndex) =>
<h1 key={ itemIndex }>{ item }</h1>
))
}
</>
}
</div>
)
}

Correcting use-effect to re-render react component only once upon value loading from API

I am having trouble with a content loading issue with my react/next.js app using hooks. I believe the problem is with my useEffect and the dependency. I would appreciate any unblockin assistance. The situation:
As you will see from the code below:
In the useEffect:
'eventType' object loads from one API endpoint and is added to the corresponding state
if eventType is a movie (has externalData key) then we load the movie object from another API endpoint based on the filmId (in externalData)
so if eventType.externalData is not null then we set the movie state value to the content of the response data
if movie state is true then we render the 'movie' code block
else we render the 'non-movie' code block
The issue:
For some reason the movie information is not coming back fast enough from the API call, so when given the choice of what to render, the app renders the non-movie block
In order to counteract this, I added [movie] to the dependencies for the useEffect. This works, but leads to infinite reload hell.
My question:
How can I make sure that the reload is triggered only once when the movie data loads and not infinitely?
function EventDetailPage(props) {
const router = useRouter();
const slug = router.query.slug;
const cinemaId = router.query.cinemaId;
const date = slug[0];
const eventTypeId = slug[1];
const {locale} = useRouter();
const [eventType, setEventType] = useState(null);
const [movie, setMovie] = useState(null);
const [personData, setPersonData] = useState(null);
const {user} = useUserStore();
const setupEventDetailPageWithData = async () => {
try {
const eventType = await getEventTypeByCinemaIdAndDate(cinemaId, date, eventTypeId);
setEventType(eventType);
console.log('eventType state:', eventType);
if (!user){
const personDataResponse = await fetchPersonData();
setPersonData(personDataResponse);
}
if (eventType.events[0].userId === personData.userId){
console.log("Event initiator id:", eventType.events[0].userId);
setIsInitiator(true);
}
if (eventType.externalData) {
const movie = await getMovieById(eventType.externalData.filmId);
setMovie(movie);
} else {
}
} catch (error) {
// handle error
console.error(error);
}
};
useEffect(() => {
setupEventDetailPageWithData();
}, [movie]);
if (!eventType) {
return (
<>
<LoadingAnimation/>
</>
);
}
if (movie) {
return ( <div> movie code </div> )
} else {
return ( <div> non-movie code </div> )
}
you can do somethings like this
useEffect(() => {
//call api and set your state
},[])
or better practise like this
useEffect(() => {
//call your api and set your state
},[setMovie])

Why my hook state doesn´t update correctly?

i don't know how make this guys, i can't update my state with the api array, and if i put it in useEffect i have an error cause i am not sending any data, help me please is my first time using stackoverflow
import React, { useEffect, useState } from "react";
import getTeam from "../Helpers/getTeam";
const selectTeams = [
"Barcelona",
"Real Madrid",
"Juventus",
"Milan",
"Liverpool",
"Arsenal",
];
const Select = () => {
const [team, setTeam] = useState(null);
const [loading, setLoading] = useState(null);
const handleOption = async (e) => {
setLoading(true);
let teamsJson = await getTeam(e.target.value);
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
console.log(arr);
console.log(team);
setTeam(arr);
setLoading(false);
};
return (
<div
style={{ background: "skyblue", textAlign: "center", padding: "20px" }}
>
<h1>Equipos Disponibles</h1>
<div>
<select onChange={handleOption}>
<option>Elige tu equipo</option>
{selectTeams.map((selectTeam, i) => {
return <option key={i}>{selectTeam}</option>;
})}
</select>
</div>
{loading ? <h1>suave</h1> : (
team !== null ? (
team.map((newTeam, i) => {
return (
<div>
the items are here
</div>
)
})
) : null
)}
</div>
);
};
export default Select;
i let you my api file down
const getTeam = async (teamName) => {
const url = `https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${teamName}`;
const res = await fetch(url);
const team = await res.json();
return team;
};
export default getTeam;
i wanna update my const team with the response of my api call, but it doesn't update it, i dont know what do, please help me
The teamsJson value is an object with a single key and value of some array
{ teams: [...] }
So you are updating your state with a nested array when you push the value into another array.
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
Based upon how you want to map your team state array I assume you just want the raw inner array from teamJson.
const { teams } = await getTeam(e.target.value);
setTeam(teams);
Then when you are mapping you can access any of the properties you need.
team.map((newTeam, i) => {
return <div key={i}>{newTeam.idTeam}</div>;
})
I've just tested it & it seems to works just fine.
The only 2 issues seem to be that:
You don't use team anywhere (apart from a console.log statement).
At the moment when you console.log(team); the constant team will (yet) be null for the first time (because it still keeps the initial state).
Here's what I see in React dev tools after picking a random team in the <select>:

Loading spinner not showing in React Component

I am creating a React.js app which got 2 components - The main one is a container for the 2nd and is responsible for retrieving the information from a web api and then pass it to the child component which is responsible for displaying the info in a list of items. The displaying component is supposed to present a loading spinner while waiting for the data items from the parent component.
The problem is that when the app is loaded, I first get an empty list of items and then all of a sudden all the info is loaded to the list, without the spinner ever showing. I get a filter first in one of the useEffects and based on that info, I am bringing the items themselves.
The parent is doing something like this:
useEffect(() =>
{
async function getNames()
{
setIsLoading(true);
const names = await WebAPI.getNames();
setAllNames(names);
setSelectedName(names[0]);
setIsLoading(false);
};
getNames();
} ,[]);
useEffect(() =>
{
async function getItems()
{
setIsLoading(true);
const items= await WebAPI.getItems(selectedName);
setAllItems(items);
setIsLoading(false);
};
getTenants();
},[selectedName]);
.
.
.
return (
<DisplayItems items={allItems} isLoading={isLoading} />
);
And the child components is doing something like this:
let spinner = props.isLoading ? <Spinner className="SpinnerStyle" /> : null; //please assume no issues in Spinner component
let items = props.items;
return (
{spinner}
{items}
)
I'm guessing that the problem is that the setEffect is asynchronous which is why the component is first loaded with isLoading false and then the entire action of setEffect is invoked before actually changing the state? Since I do assume here that I first set the isLoading and then there's a rerender and then we continue to the rest of the code on useEffect. I'm not sure how to do it correctly
The problem was with the asynchronicity when using mulitple useEffect. What I did to solve the issue was adding another spinner for the filters values I mentioned, and then the useEffect responsible for retrieving the values set is loading for that specific spinner, while the other for retrieving the items themselves set the isLoading for the main spinner of the items.
instead of doing it like you are I would slightly tweak it:
remove setIsLoading(true); from below
useEffect(() =>
{
async function getNames()
{
setIsLoading(true); //REMOVE THIS LINE
const names = await WebAPI.getNames();
setAllNames(names);
setSelectedName(names[0]);
setIsLoading(false);
};
getNames();
} ,[]);
and have isLoading set to true in your initial state. that way, it's always going to show loading until you explicitly tell it not to. i.e. when you have got your data
also change the rendering to this:
let items = props.items;
return isLoading ? (
<Spinner className="SpinnerStyle" />
) : <div> {items} </div>
this is full example with loading :
const fakeApi = (name) =>
new Promise((resolve)=> {
setTimeout(() => {
resolve([{ name: "Mike", id: 1 }, { name: "James", id: 2 }].filter(item=>item.name===name));
}, 3000);
})
const getName =()=> new Promise((resolve)=> {
setTimeout(() => {
resolve("Mike");
}, 3000);
})
const Parent = () => {
const [name, setName] = React.useState();
const [data, setData] = React.useState();
const [loading, setLoading] = React.useState(false);
const fetchData =(name) =>{
if(!loading) setLoading(true);
fakeApi(name).then(res=>
setData(res)
)
}
const fetchName = ()=>{
setLoading(true);
getName().then(res=> setName(res))
}
React.useEffect(() => {
fetchName();
}, []);
React.useEffect(() => {
if(name)fetchData(name);
}, [name]);
React.useEffect(() => {
if(data && loading) setLoading(false)
}, [data]);
return (
<div>
{loading
? "Loading..."
: data && data.map((d)=>(<Child key={d.id} {...d} />))}
</div>
);
};
const Child = ({ name,id }) =>(<div>{name} {id}</div>)
ReactDOM.render(<Parent/>,document.getElementById("root"))
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Resources