why wont jsx render firestore data - javascript

I'm trying to GET data from firestore and render it, in the most basic way possible. I can console.log the data, but as soon as I try to render it via JSX, it doesn't. Why is this?
import React from 'react'
import { useState, useEffect } from 'react'
import {db} from '../../public/init-firebase'
export default function Todo() {
const [todo, setTodo] = useState()
const myArray = []
//retrive data from firestore and push to empty array
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
}
useEffect(() => {
getData()
}, [])
return (
<>
<div>
<h1>Data from firestore: </h1>
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
</div>
</>
)
}

First, change myArray to State like this:
const [myArray, setMyArray] = useState([]);
Every change in myArray will re-render the component.
Then, to push items in the State do this:
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
setMyArray(oldArray => [...oldArray, doc.id])
})
})
}
You're just pushing the ID in myArray, so when to show try like this:
{myArray.map((id) => {
console.log('hi')
return <h1 key={id}>{id}</h1>
})}

If you look closely,
{myArray.map((doc) => {
<h1>{doc.id}</h1>
console.log('hi')
})}
those are curly braces {}, not parenthesis (). This means that although doc exists, nothing is happening since you are just declaring <h1>{doc.id}</h1>. In order for it to render, you have to return something in the map function. Try this instead:
{myArray.map((doc) => {
console.log('hi')
return <h1>{doc.id}</h1>
})}

In order to force a "re-render" you will have to use the hooks that you defined
https://reactjs.org/docs/hooks-intro.html
function getData(){
db.collection('Todos')
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id +'pushed to myArray')
myArray.push(doc.id)
})
})
// in case the request doesn't fail, and the array filled up with correct value using correct keys:
// i'd setState here, which will cause a re-render
setTodo(myArray)
}

Related

React returning empty state

I understand that useEffect() hook is supposed to fire on the first render. In my code, I am getting errors for not being able to use my States properties. This is because the State doesn't have the API data before the DOM renders. I guess I am just not understanding if I am missing something, but the console log shows that the questions array is empty twice before actually getting any data. If I am not trying to access the data anywhere, no data is thrown, but as soon as I try to use it, the code says that the data doesn't exist. I tried to use async but get the same error.
export default function Quiz(props) {
const [questions, setQuestions] = React.useState([])
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=10")
.then(res => res.json())
.then(data => setQuestions(data.results))
}, [])
console.log(questions)
return (
<div>
<div className="quiz--question">
<h1>{questions.category}</h1>
</div>
</div>
)
}
questions is an array , so you need to iterate that to display the value. You can use map
import * as React from 'react';
import './style.css';
export default function App() {
const [questions, setQuestions] = React.useState([]);
React.useEffect(() => {
fetch('https://opentdb.com/api.php?amount=10')
.then((res) => res.json())
.then((data) => {
setQuestions(data.results);
console.log(questions);
});
}, []);
return (
<div>
{questions.map((elem) => (
<div className="quiz--question">
<h1>{elem.category}</h1>
</div>
))}
</div>
);
}
Demo Here

why useEffect is called infinitely

In useEffect in my react component I get data and I update a state, but I don't know why the useEffect is always executed:
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect( async()=>{
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
},[studies]);
return(
<Comp2 studies={studies}/>
)
}
Here is my second Component used in the first component...
const Comp2 = (props) => {
const [studies, setStudies]= useState([]);
React.useEffect( ()=>{
setStudies(props.studies)
},[props.studies, studies]);
return(
studies.map((study)=>{console.log(study)})
}
EDIT
const Comp2 = (props) => {
// for some brief time props.studies will be an empty array, []
// you need to decide what to do while props.studies is empty.
// you can show some loading message, show some loading status,
// show an empty list, do whatever you want to indicate
// progress, dont anxious out your users
return (
props.studies.map((study)=>{console.log(study)}
)
}
You useEffect hook depends on the updates that the state studies receive. Inside this useEffect hook you update studies. Can you see that the useEffect triggers itself?
A updates B. A runs whenever B is updated. (goes on forever)
How I'd do it?
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect(() => {
const asyncCall = async () => {
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
}
asyncCall();
}, []);
return(
<Comp2 studies={studies}/>
)
}
useEffect() has dependency array which causes it to execute if any value within it updates. Here, setStudies updates studies which is provided as dependency array and causes it to run again and so on. To prevent this, remove studies from the dependency array.
Refer: How the useEffect Hook Works (with Examples)

Browser freezing when using React Hook

When I enter into a router that refers to this Component, my browser just crashes. I've made some console tests and notices that when the response.data.message is up, it continually re-renders the page. Can someone help me?
import React from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
return (
<div>
{fetchUsername()}
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
On every render, fetchUsername() is called and results in updating statue and stories, which results in another rerender, and thus leads to an infinite loop of rerendering (since every render triggers a state update).
A better practice for handling functions with side-effects like fetching data is to put the fetchUsername in useEffect.
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {})
.then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
});
}, []); // An empty denpendency array allows you to fetch the data once on mount
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
You can't call function fetchUsername() inside return statement. It
will go in infinite loop.
You can't directly set the state of two variable as setStatue(response.data.statue) & setStories(response.data.message) respectively
use the useEffect hooks and set the status inside that hooks with conditional rendering to avoid looping.
call the fetchUsername() just before the return statement.
I looked at your code carefully. As you can see in this code, the fetchUsername() function is executed when the component is first rendered.
When you call setState() in the fetchUsername function, your component's state variable is updated. The problem is that when the state variable is updated, the component is rendered again. Then the fetchUsername function will be called again, right?
Try the useEffect Hook.
The example code is attached.
eg code
How about trying to change the way you call the fetchUsername() function with the useEffect hook instead?
import React, { useEffect } from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
fetchUsername();
// clean up here
return () => {
// something you wanna do when this component being unmounted
// console.log('unmounted')
}
}, [])
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
But, sometimes your code just doesn't need a cleanup in some scenarios, you could read more about this here: Using the Effect Hook - React Documentation.

React, using useEffect to updata data, a data is a Array, but when I use map or Object.keys(data).map, both are not work. How to make it work?

I used the useEffect to updata the data, I can see the data is a array in DEV tool, but when I using map to traverse the data, will show be that data.map is not a function. If I use Object.keys(data).map, there are not thing was showed, but no error...
How to fix ...?
export default function List() {
let [data, setData] = useState();
useEffect(async () => {
let result = await loadList();
console.log(result.data.data);
setData({ data: result.data.data });
return;
}, data);
return (
<div>
{
(data) && data.map((item) => {
return <div key={item.id} >{item.title} author: {item.author} Creact time: { item.creactetime}</div>
})
}
</div>
)}
useEffect does not allow the use of an async function. Instead you should just use the then method when calling your loadList function and set the data in the callback.
It seems that useEffect doesn't need a dependency as it only needs to get the data on the first render, not when data changes. The latter would result in an infinite loop (Update data => do something when data updates => update data)
In setData just pass the result.data.data to update data, not within another object. Otherwise the data will not be an array, but an object.
export default function List() {
let [data, setData] = useState(null);
useEffect(() => {
loadList().then((result) => {
setData(result.data.data);
});
}, []);
return (
<div>
{data &&
data.map((item) => (
<div key={item.id}>
{item.title} author: {item.author} Creact time: {item.creactetime}
</div>
))}
</div>
);
}
useEffect can not have an async callback you need to wrap it like this
useEffect(()=>{
const fetch = async () => {
const result = await loadList();
setData(result.data.data);
}
fetch();
}, []);
data must be an array if you want to map on it, you can try to set directly data with result.data.data if it's an array, like following :
useEffect(async () => {
let result = await loadList();
setData(result.data.data);
}, []);

How to update a function with useEffect (like componentDidUpdate) and use .map function to display from JSON file ReactJS

I've been trying to update a function once data has been recieved and set to a state (using useState). After that the function will use the .map function display the data into a template.
However i am getting two errors, one is 'projects.map is not a function' (btw projects is my state name, where data is stored) and inside the useEffect function which updates when projects is changed 'Expected an assignment or function call and instead saw an expression'
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
// setting my state
const [projects, setProjects] = useState([])
// getting the data from some dummy online data when the app starts
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects({ data }))
}, []);
// makeing a function call postList, which stores a ternery operator
const postList = () => {
// The ternery operator asks if there is anything inside the porjects state
projects.length ? (
// If there is something in the state, it will map out the JSON array in the 'projectSummary template
projects.map(projects => {
return(
<div >
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body}/>
</div>
)
})
) : (
// If there isnt anything in the state is prints out 'Loading Data'
<h1>Loading Data</h1>
);
}
// useEffect updates when the 'projects' stae is updated (like componentDidUpdate, and runs the function again
useEffect(() => {
postList()
}, [projects]);
return(
<div className="ProjectList">
// The component should output the postList function, which should map out the array, in the template
{ postList }
</div>
)
}
export default ProjectList
You need to make some corrections to your component
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
const [projects, setProjects] = useState([])
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects(data))
}, []);
return(
<div className="ProjectList">
{
projects.length ?
projects.map(projects => (
<div>
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body} />
</div>
))
:
<h1>Loading Data</h1>
}
</div>
)
}
export default ProjectList;
You don't need the postList function and the second useEffect.
You may want to add additional checks to determine when the posts are loading and when they're empty after loading is done, so you don't just get a loading message
try this
const [projects, setProjects] = useState([])
and
.then(data => setProjects(data))
assuming that the data is an array
First you have to set the initial projects to an empty array like this:
const [projects, setProjects] = useState([])
, because currently projects is an empty string, which does not have the map function.
For the fetch, you should write it like this:
useEffect(async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
setProjects(data);
}, []);
Hope this helps.
Array.prototype.map() is for Array.
const [projects, setProjects] = useState('');
projects not an array.

Categories

Resources