When I render this React component:
import React, { useState, useEffect, useCallback } from "react";
// import RenderLineChart from "../recharts/rechart.component";
import axios from "axios";
import "./chart-item.styles.scss";
const ChartItem = ({ apiUrl }) => {
const [chartData, setChartData] = useState([]);
//function that pulls data from APIs
const loadChartData = useCallback(() => {
axios.get(apiUrl).then((response) => {
setChartData(response.data);
});
}, [apiUrl]);
//runs initial pull from API
useEffect(() => {
loadChartData();
}, [loadChartData]);
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
// console.log("Array Element: ");
// console.log(chartData.historical[0]);
return <div className="chart"></div>;
};
export default ChartItem;
The console output is such:
When I uncomment that third console.log statement and save the component file without refreshing localhost, the output is:
But when I actually refresh the localhost tab, the output is such:
As if it is no longer being treated as an array. Can anyone explain what's going on here?
You have 2 mistakes in the snippet you posted.
You're declaring chartData as an array, but then you're updating it with an object (inside the loadChartData function)
When page loads the console.log is executed before the loadChartData function has completed the request, so at the first render chartData = [] and chartData[0] = undefined, but if you try to get chartData.historical[0] it throws you an error because it is defined as an array.
So, how can you fix this? The answer is pretty straightforward, first of all you have to use a consistend data-type for your state, if it's an array when declared, then when you update it, pass an array, if not you'll always have this kind of problems. This should fit your needs. If you want to log chartData everytime it upates you just need to write a new useEffect with the console.log inside and chartData as "dependency"
const [chartData, setChartData] = useState({ historical: [] })
// declare you api call
const apiCall = useCallback(() => {
try {
const response = await axios.get(url);
setChartData(response.data)
} catch (e) {
console.error(e)
}
}, [setChartData])
useEffect(() => {
apiCall()
}, [apiCall])
useEffect(() => {
console.log("Raw Data: ");
console.log(chartData);
console.log("Array: ");
console.log(chartData.historical);
console.log("Array Element: ");
console.log(chartData.historical[0]);
}, [chartData])
// do you stuff with jsx
I suggest you to take a look at Hooks Documentation
Hope this helps ✌️
Related
I have a collection in firebase called "polls". The collection is an array, and within the array I have 4 urls (url_a, url_b, url_c, and url_d). I want to pull these urls out of the array in my react app so I can use them as the src for 4 different images.
I know since "polls" is an array, I can't just pull the urls directly by setting some variable to "polls.data.url_a". With the 2nd useEffect function in the code below, I'm able to console.log the array
and the data within it using forEach. I can console.log poll.data, and even poll.data.url_a and it returns the proper result.
But I am stuck on how to get outside of that forEach and actually capture the individual urls to where I can assign them to a variable so I can make use of them.
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
** useEffect(() => {
polls.forEach((poll) =>
console.log(poll.data.url_a))
}, [polls])
**
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
I've tried setting a variable within the forEach and calling it later, and running a forEach later on in the function. But I just don't know how to get to the URL in a usable way, outside of just console logging it. Any help would be greatly appreciated, thanks!
Check out what Renaud suggested here to see how to assign variables: Get Firebase Collection into simple array
If I understand what you're asking, you'll need the poll const to instead point towards a global store variable or to reference it within the template section.
If the data your fetching is structured like this:
const polls = [{
data: {
url_a: "url a",
url_b: "url b"
}
}]
It's easier to just assign it to an object state variable instead of an array.
const [polls, setPolls] = useState({})
// ...
useEffect(() => {
getPolls()
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const polls = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
polls.map(poll => setPolls(poll.data))
}).catch(error => console.log(error.message))
}
// ...
return <img src={polls.url_a} alt="" />
I Have a list containing some ids, I have to call an api with each of ids in the list and store all the data that comes from the api in a list,I am mapping through the list and calling the api with each id and then pushing the data into an array,but when I finally check the array it gives inconsistent result,sometimes it returns all the data,some time some of the data or sometimes the list is empty,here is my react code
let deviceidlist=['eb77fa554fbdbed47dkubk','ebbaa8d217947ff4d1fk3w','ebe55d36879c7fd71emtf0','eb645acaa1efb456877nss','ebc32ad422bc5dc3eecapa','ebf5bb435e688e96e8mt5z','45102754e09806133d2d','eb7c72ba426f92b9a1pb81','eb84a574ecfa372e6ccapr','eb458f73adadf67170uxdv']
let devicelist=[]
useEffect(()=>{
const datafetch=async()=>{
deviceidlist.map((item)=>{fetch(`http://localhost:8000/api/switch/${item}`).then(data=>data.json()).then(data=>devicelist.push(data))})
}
datafetch()
}
,[])
console.log(devicelist)
I am trying to store all the data that I get back from api to store in a list but getting an empty array
Fetching data from API is an asynchronous task, consoling right after the API call may not give the expected result. That's the reason you are getting multiple results
try adding the deviceList to a state so it will be
const[deviceList, setDeviceList] = useState([]);
useEffect(()=>{
const datafetch=async()=>{
deviceidlist.map((item)=>{fetch(`http://localhost:8000/api/switch/${item}`).then(data=>data.json()).then(data=>{
setDeviceList([...deviceList, data]);
})})
}
datafetch()
}
,[])
Try consoling the deviceList (state update will re-render the component and will console the updated data)
Try to fetch the data asynchronously and then set it to the state instead of pushing it into the array. Something like this :
import React from 'react';
import { useEffect } from 'react';
export default function App() {
let deviceidlist = [
'eb77fa554fbdbed47dkubk',
'ebbaa8d217947ff4d1fk3w',
'ebe55d36879c7fd71emtf0',
'eb645acaa1efb456877nss',
'ebc32ad422bc5dc3eecapa',
'ebf5bb435e688e96e8mt5z',
'45102754e09806133d2d',
'eb7c72ba426f92b9a1pb81',
'eb84a574ecfa372e6ccapr',
'eb458f73adadf67170uxdv',
];
const deviceList = [];
useEffect( async() => {
try {
let response = await deviceidlist.map((item) => { fetch(`http://localhost:8000/api/switch/${item}`) })
if (response.ok) {
let data = await response.json()
deviceList.push(data)
} else {
console.log("failed")
}
} catch(error) {
console.log(error)
}
})
return (
<div></div>
);
}
I have the following code in my React component:
const { id } = useParams();
const { tripData, facilityData } = useContext(AppContext);
const [data, setData] = useState([]);
useEffect(() => {
const idResults = facilityData.filter(facility => facility.id === id);
if (idResults.length > 0) {
setData(idResults[0]);
}
}, [])
Where:
[data, SetData] is the state that is used to handle populating a container
facilityData is data accessed from my app context
id is accessed from the URL
What seems to happen is that the data loads the first time without fault, but it errors out when hosted on the actual site (on localhost, it waits and eventually loads). To try to get a better idea of what was happening, I tried the following code:
const { id } = useParams();
const { tripData, facilityData } = useContext(AppContext);
const [data, setData] = useState([]);
useEffect(() => {
const idResults = facilityData.filter(facility => facility.id === id);
if (idResults.length > 0) {
setData(idResults[0]);
} else if (idResults.length === 0) {
console.log(`id: ${id}`)
console.log(`len: ${idResults}`)
}, [])
On localhost, on refresh, it console logs the actual id but then console logs the empty array before finally loading the data.
What I'm wondering is why this is the observed behavior. The "id" value seems to be constantly available, but the filter doesn't seem to run prior to the site loading. Is there a way to prevent this?
EDIT:
This is how I get the data (from Firebase)
App.js
import { collection, getDocs } from "firebase/firestore";
import { db } from "./firebase";
const [truckData, setTruckData] = useState([]);
const [facilityData, setFacilityData] = useState([]);
const [tripData, setTripData] = useState([]);
useEffect(() => {
const fetchData = async (resource, setter) => {
let list = [];
try {
const querySnapshot = await getDocs(collection(db, resource));
querySnapshot.forEach((doc) => {
let docData = doc.data();
if (resource === "trips") {
docData.startDate = docData.startDate.toDate();
docData.endDate = docData.endDate.toDate();
}
list.push({ id: doc.id, ...docData });
});
setter(list);
} catch (error) {
console.log(error);
}
};
fetchData("trucks", setTruckData);
fetchData("facilities", setFacilityData);
fetchData("trips", setTripData);
}, []);
The app is at logi-dashboard, if that helps any.
EDIT Turns out the issue was with my hosting service, not the project. Go figure.
Based on my understanding, it seems like the facilityData on which you are trying to apply filter and which is coming from AppContext(Context hook variable) is found to be empty array when the useEffect code is getting executed, this might be scene if you are hitting any API to get the data into facility but the API response is not coming till the time useEffect is getting executed or any other source which is not populating the facilityData until useEffect runs.
In that case, you can add facilityData in the dependency array of useEffect, which will help the useEffect execute again once the facilityData is populated(updated)
I have two components which i am working with. In the first component, i made a custom useEffect hook that retrieves data from my server. Please see the code below:
Code snippet One
import {useState, useCallback} from 'react';
import {stageQuizApi} from '../api/quiz';
import {QuestionService} from "../services/IdDbServices/question_service";
const usePostData = ({url, payload, config}) => {
const [res, setRes] = useState({data: null, error: null, isLoading: false});
const callAPI = useCallback(() => {
setRes(prevState => ({...prevState, isLoading: true}));
stageQuizApi.patch(url, payload, config).then( res => {
setRes({data: res.data, isLoading: false, error: null});
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
}).catch((error) => {
console.log(error)
if (error.response) {
const errorJson = error.response.data
setRes({data: null, isLoading: false, error: errorJson.message});
} else if (error.request) {
setRes({data: null, isLoading: false, eror: error.request});
} else {
setRes({data: null, isLoading: false, error: error.message});
}
})
}, [url, config, payload])
return [res, callAPI];
}
export default usePostData;
The above module has two purpose. It first makes an axios request to my endpoint and secondly makes a database insertion to browser IndexDb (similar to localstorage but with sql approach) ( like inserting data into the database using the response that was gotten from the first request. so typically i have a promise in the outer .then block. This part:
Code snippet Two
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
Here is the problem, I am trying to maintain state as i want the result of this module to be shared in another route and i don't want to hit the server again hence i inserted the result into indexDb browser storage. Here is the code that executes the above module:
Code snippet Three
const displaySingleQuestion = ()=>{
OnUserGetQuestion();
history.push('/player/question');
}
The above method is called from my first route /question and it is expected to redirect user to the /player/question when the displaySingleQuestion is called.
On the new route /player/question i then want to fetch the data from IndexDb and update the state of that component using the useEffect code below:
Code snippet Four
useEffect(()=>{
const getAllUserFromIndexDb = async()=>{
try{
const result = await new Promise((resolve, reject) => {
service.getStudents().then(res=>resolve(res)).catch(err=>reject(err))
});
console.log('it did not get to the point i was expecting',result)
if(result[0]){
console.log('it got to the point i was expecting')
const singleQuestion = result[0];
const questionPage = playerQuestionToDisplay;
questionPage.name = singleQuestion.name;
questionPage.anwserRatio = singleQuestion.anwserRatio;
questionPage.answer = singleQuestion.answer;
questionPage.options = singleQuestion.options;
questionPage.playerId = singleQuestion.playerId;
questionPage.question = singleQuestion.question;
questionPage.timePerQuestion = singleQuestion.timePerQuestion;
return setplayerQuestionToDisplay({playerQuestionToDisplay:questionPage})
}
}
catch(error){
console.log(error)
}
}
getAllUserFromIndexDb();
return function cleanup() {
setplayerQuestionToDisplay({playerQuestionToDisplay:{}})
}
},[history.location.pathname]);
The problem is that only one Button click (Code snippet three)(displaySingleQuestion()) triggers the whole functionality and redirect to the /player/question page but in this new route the state is not been set until a page reload as occurred, i tried debugging the problem and i found out that when the button is clicked i found out that Code snippet two is executed last hence when Code snippet Four ran it was in promise and until a page reloads occurs the state of the component is undefined
Thanks for reading, Please i would appreciate any help in resolving this issue.
I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.