Multiple fetch requests with setState in React and pie chart - reactjs

My query was taking more than 2 mins to execute, henceforth it was getting timeout in browser. So now I have break the query and now running as a separate APIs which is helpful, but now I don't know how to handle these three requests so that it can render the data.
Note: The API's data are getting stored in the State component of react, here it is "Data".
Now I have a logic but can anyone give me a direction how to implement it.
Logic: Before storing the result of API's directly into state component, we can store it into different array, then we can iterate through this array for the use of pie chart then this data can be stored into the state component which can be used to render the pie chart in "Render" function.
Here the I am making three different API calls at the same time and storing it, here the result of the API's are directly been stored in the state component:
componentDidMount() {
Promise.all([
fetch("http://localhost:4000/api/EMEA/E_claimQuarter"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter1"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter2")
])
.then(([res1, res2, res3]) =>
Promise.all([res1.json(), res2.json(), res3.json()]))
.then(([data1, data2, data3]) =>
this.setState({
// Data: data1, data2, data3,
Data: {
labels: [
"FY19 Q1[NOV-JAN]",
"FY19 Q2[FEB-APR]",
"FY18 Q3[SEP-NOV]"
],
datasets: [
{
label: "",
data: data1,
backgroundColor: [
"rgba(255,105,145,0.6)",
"rgba(155,100,210,0.6)",
"rgb(63, 191, 191)"
]
}
]
}
})
);
}
This is how you handle the data form API and loop through it then render this data for the various charts which is in my case is Pie Chart:
ComponentDidMount() {
axios.get(`http://localhost:4000/api/APJ/A_claimQuarter`)
***************************************************************
.then(res => {
const claims = res.data;
let claim = [];
claims.forEach(element => {
claim.push(element.CNT1);
});
********************************************************************
this.setState({
Data: {
labels: ['FY19 Q1[NOV-JAN]','FY19 Q2[FEB-APR]','FY18[SEP-NOV]'],
datasets:[
{
label:'',
data: claim ,
backgroundColor:[
'rgba(255,105,145,0.6)',
'rgba(155,100,210,0.6)',
'rgb(63, 191, 191)'
]
}
]
}
});
})
}

I have made some modifications and now it is working fine for me, if anyone want the answer you can look at mine, it is 100% working:
constructor(props) {
super(props);
this.state = {
Data: []
};
}
componentDidMount() {
Promise.all([
fetch("http://localhost:4000/api/EMEA/E_claimQuarter"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter1"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter2")
])
.then(([res1, res2, res3]) => Promise.all([res1.json(), res2.json(), res3.json()]))
.then(([data1, data2, data3]) =>
{
console.log(typeof(data1));
const array = [...data1, ...data2, ...data3];
// const A = JSON.strigify(array);
console.log('hi');
console.log(array);
console.log(data1);
// console.log(A);
let claim = [];
array.forEach(element => {
claim.push(element.COUNT);
});
console.log(claim);
this.setState({
// Data: data1, data2, data3,
Data: {
labels: [
"FY19 Q1[NOV-JAN]",
"FY19 Q2[FEB-APR]",
"FY18 Q3[SEP-NOV]"
],
datasets: [
{
label: "",
data: claim,
backgroundColor: [
"rgba(255,105,145,0.6)",
"rgba(155,100,210,0.6)",
"rgb(63, 191, 191)"
]
}
]
}
})
});
}

Based on OP's own answer, here's a more generalised solution :
componentDidMount(graphData) {
return Promise.all(graphData.map(dataObj => dataObj.url))
.then(results => Promise.all(results.map(res => res.json())))
.then(results => this.setState({
'Data': {
'labels': graphData.map(dataObj => dataObj.label),
'datasets': [
{
'label': '',
'data': results.reduce((prev, next) => prev.concat(next), []),
'backgroundColor': graphData.map(dataObj => dataObj.bgColor)
}
]
}
}));
}
As you see, Array methods .map() and .reduce() make for some nice compact code.
Call as follows:
var quartersData = [
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter', 'label':'FY19 Q1[NOV-JAN]', 'bgColor':'rgba(255,105,145,0.6)' },
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter1', 'label':'FY19 Q2[FEB-APR]', 'bgColor':'rgba(155,100,210,0.6)' },
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter2', 'label':'FY18 Q3[SEP-NOV]', 'bgColor':'rgb(63, 191, 191)' }
];
componentDidMount(quartersData)
.then(() => {
console.log('complete');
});

Related

Pushing data to an array in already existing object with axios

i have a object which looks like this:
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
}
to an characters array i want to add the id of characters.
I do it by form and then i handle submit like this:
const handleSubmit = (values) => {
console.log("dodano aktora do filmu!");
console.log(values);
addActorToMovie(values);
history.goBack();
};
the addActorToMovie action:
export const addActorToMovie = (resp) => ({
type: types.ADD_CHAR_TO_MOVIE,
payload: resp,
});
and the reducer:
case types.ADD_CHAR_TO_MOVIE:
console.log(action.payload);
return {
...state,
...state.episodes.map(function (item) {
return item.id === action.payload.episodeId
? {
id: item.id,
title: item.title,
release_date: item.release_date,
series: item.series,
img: item.img,
characters: [...item.characters, action.payload.actor],
}
: { ...item };
}),
};
It all works, but the problem is that i dont want to do it loccaly. Im using an database with json-server, and I want to do an Axios Request so that it would add a data to the database.
And i don't know how to do this, when i use axios.post it adds an object to my episodes array, if im using axios.put it changes an object. Is there any possibility to push the data to an array as i do it with the code above, but with axios so that it would be added to database?
My approach looked like this:
export const addActorToMovieAxios = (value) => {
console.log(value);
return async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:3000/episodes/`,
value
);
console.log(response);
dispatch(addActorToMovie(response.data));
} catch (ex) {
console.log(ex);
}
};
};
but as I said this does add a new object to an array.....
"episodes": [
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
},
{
"episodeId": 1,
"actor": "1",
"id": 2
}
]
So just to be clear I understand your question, you have an object that already exists in your DB, and you want to push something onto the 'characters' array in that existing object, without creating a new object, correct?
To do this, I would use Mongo for your DB and define two Mongoose Schemas, one for the existing object (let's call it TVShow) and one for the Characters within that object. Your two Schemas will look like this:
TVShowModel.js:
const mongoose = require('mongoose');
const CharacterModel = require('./CharacterModel')
const TVShowScheme = new mongoose.Schema({
title: {
type: String,
},
release_date: {
type: Date,
},
series: {
type: String,
},
img: {
type: String,
},
characters:[
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
},
],
examQuestions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'CharacterModel'
}
]
})
module.exports = mongoose.model('TVShowModel', TVShowScheme )
CharacterModel.js:
const mongoose = require('mongoose');
const CharacterModel= new mongoose.Schema({
characterName: {
type: String,
},
actorName: {
type: String,
},
}) // add any other fields you want here
module.exports = mongoose.model('CharacterModel', CharactModelScheme )
Then, create your Axios post request. Make sure you send when you send the 'value' variable to your server, it contains the id (or perhaps the unique title) of the object you'll be 'pushing' to. Push won't work in axios/react, so we'll use the 'spread' opperator instead.
Your router will look like this:
const CharacterModel= require ('../models/CharacterModel');
const TVShowModel= require ('../models/TVShowModel');
const router = express.Router();
router.post('/episodes', async function(req,res){
try{
const tvshow = await TVShowModel.find({title: req.body.title})
// edit as needed
console.log("FOUND TV Show: "+tvshow )
const characterName= req.body.characterName
const actorName = req.body.actorName
const newCharacter = new CharacterModel({
characterName,
actorName,
})
console.log("new character created: "+newCharacter)
tvshow[0].CharacterModel = [...tvshow[0].CharacterModel,newCharacter];
await tvshow[0].save()
.then(()=>res.json('New Character Added to DB'))
.catch(err=>res.status(400).json('Error: ' + err))
} catch(e){
console.log(e)
}
})
Hope this was clear!

React pass API data to chart

Background
I have an chart which displays static data just fine.
Using this template https://github.com/creativetimofficial/black-dashboard-react/blob/master/src/variables/charts.js
On my main page [dash.js] I have an API call, (which I tested presents the data I expect by using console.log().
I will be looking to have this data working dynamically so I have created it using useEffect and useState.
For reference;
const [chrtState, setChrtState] = useState({
loading: false,
chartos: null,
});
useEffect(() => {
setChrtState({loading: true});
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then(res => {
setChrtState({loading: false, repos: res.data.characters});
});
}, [setChrtState]);
const setCanvas = name => {
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then(res => {
setChrtState({loading: false, chartos: res.data.characters});
//console.log(res.data.characters);
});
};
return (
<Line
data={chartExample1[bigChartData + bigChartTime]}
options={chartExample1.options}
apiprops={chrtState.chartos}
/>
);
Note: the data parameter is used to select a specific chart-type (e.g. data1, data2, etc), this part works fine and isn't related to the APIdata as such.
My Problem
I am struggling to work out how to pass the API data to the chart.js
I tried using some other examples of how to pass props but it is proving very confusing for me given that it is already passing data1: (canvas) etc.
What I've tried
I tried to add an additional parameter before data1 (line 77) in charts.js, as follows;
apiprops: (props) => {
const {repos} = props;
console.log(repos);
},
but nothing was printed to the console for this.
I tried adding the data to canvas but this is already passing information used to render the height, width and style of the of the chart.
I have tried to add the API to the charts.js file, however when I add import axios from 'axios'; to the top of this page it throws out a syntax error. But I think it makes more sense to pull the API elsewhere and pass as a prop anyway. (please let me know if you disagree).
I am very much still building my knowledge of reactjs so thank you for any help and guidance on this!
End goal
For reference, my end goal will be to pass the API data to the chart and then process each dictionary into the labels and the datasets.data - the API passes in this order
{
"characters": [
{
"label": 123,
"data": 321
},
{
"label": 456,
"data": 654
}
]
}
I understood that you are trying to inject your API values into the existing functions in charts.js. First, you need to separate the API values into two arrays: labels and data. You can do that with reduce
const values = res.data.characters.reduce(
(acc, character) => ({
labels: [...acc.labels, character.label],
data: [...acc.data, character.data],
}),
{ labels: [], data: [] }
);
setChrtState({ loading: false, repos: values });
To inject them into the functions, you'll need to modify the functions a little using currying
data1: ({labels, data}) => (canvas) => {
...
return {
labels,
datasets: [
{
...
data,
},
],
};
},
and finally, call the function when passing the data prop to the Line component
<Line
data={chartExample1[bigChartData + bigChartTime](chrtState.repos)}
Although looking at those functions they seem to have the same code, is just the data is changing, you could use a single function.
UPDATE
this would be the complete version of the component
const [chrtState, setChrtState] = useState({
loading: true,
repos: null,
});
useEffect(() => {
setChrtState({ loading: true });
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then((res) => {
const values = res.data.characters.reduce(
(acc, character) => ({
labels: [...acc.labels, character.label],
data: [...acc.data, character.data],
}),
{ labels: [], data: [] }
);
setChrtState({ loading: false, repos: values });
});
}, [setChrtState]);
if (chrtState.loading) {
return <span>Loading</span>;
}
return (
<Line
data={chartExample1[bigChartData + bigChartTime](chrtState.repos)}
options={chartExample1.options}
/>
);

React hooks async useEffect to load database data

I'm using async useEffect in React because I need to do database requests. Then, add this data to my react-charts-2
const [ plSnapShot, setPlSnapShot ] = useState({
grossIncome: 0.00,
opeExpenses: 0.00,
nonOpeExpenses: 0.00,
netIncome: 0.00,
grossPotencialRent: 0.00,
lastMonthIncome: 0.00
});
const [ thisMonthPayment, setThisMonthPayments ] = useState({
labels: [],
data: [],
color: 'blue'
});
useEffect(() => {
async function fetchData() {
await axios.get(`${url.REQ_URL}/home/getUserFullName/${userID}`)
.then(async (res) => {
setUserFullName(res.data);
await axios.get(`${url.REQ_URL}/home/getThisMonthPayments/${propertyID}`)
.then(async (resMonthPay) => {
let total = 0;
let obj = {
labels: [],
data: [],
color: 'blue'
};
const data = resMonthPay.data;
for(const d of data) {
obj.labels.push(helper.formatDate(new Date(d.date)));
obj.data.push(d.amount);
total += d.amount;
}
setThisMonthPayments(obj);
setTotalEarnedMonth(parseFloat(total));
await axios.get(`${url.REQ_URL}/home/plSnapshot/${propertyID}`)
.then(async (resPL) => {
const data = resPL.data;
setPlSnapShot({
grossIncome: parseFloat(data.GrossIncome || 0).toFixed(2),
opeExpenses: parseFloat(data.OperatingExpenses || 0).toFixed(2),
nonOpeExpenses: parseFloat(data.NonOperatingExpenses || 0).toFixed(2),
netIncome: parseFloat(data.NetIncome || 0).toFixed(2),
grossPotencialRent: parseFloat(data.GrossPotencialRent || 0).toFixed(2),
lastMonthIncome: parseFloat(data.LastMonthIncome || 0).toFixed(2)
});
});
});
});
}
fetchData();
}, [propertyID, userID]);
const pieChart = {
chartData: {
labels: ['Gross Income', 'Operating Expenses', 'Non Operating Expenses'],
datasets: [{
data: [plSnapShot.grossIncome, plSnapShot.opeExpenses, plSnapShot.nonOpeExpenses],
backgroundColor: [
ChartConfig.color.primary,
ChartConfig.color.warning,
ChartConfig.color.info
],
hoverBackgroundColor: [
ChartConfig.color.primary,
ChartConfig.color.warning,
ChartConfig.color.info
]
}]
}
};
const horizontalChart = {
label: 'Last Month Income',
labels: ['Gross Potencial Rent', 'Last Month Income'],
chartdata: [plSnapShot.grossPotencialRent, plSnapShot.lastMonthIncome]
};
Here is an example of how I call the Chart component in my code in the render/return method.
<TinyPieChart
labels={pieChart.chartData.labels}
datasets={pieChart.chartData.datasets}
height={110}
width={100}
/>
And my Pie Chart component is just to display it
import React from 'react';
import { Pie } from 'react-chartjs-2';
// chart congig
import ChartConfig from '../../Constants/chart-config';
const options = {
legend: {
display: false,
labels: {
fontColor: ChartConfig.legendFontColor
}
}
};
const TinyPieChart = ({ labels, datasets, width, height }) => {
const data = {
labels,
datasets
};
return (
<Pie height={height} width={width} data={data} options={options} />
);
}
export default TinyPieChart;
Mostly of the times it works just fine, but sometimes the chart data is loaded and displayed in the screen real quick, then it disappear and the chart is displayed empty (no data). Am I loading it properly with the useEffect or should I use another method?
Thanks you.
The momentary flashing is likely due to the fact that the chart data is empty on first render. So depending on the time it take for your useEffect to fetch the data, that flashing may present a real problem.
One common solution is to use a state variable to indicate that the data is being loaded and either not display anything in place of the chart or display a loaded of some sort. So you can add something like you suggested in the comments const [ loader, setLoader ] = useState(true). Then once the data is loaded, togged it to false.
Meanwhile, inside your render function, you would do:
...
...
{loader ?
<div>Loading....</div>
:
<TinyPieChart
labels={pieChart.chartData.labels}
datasets={pieChart.chartData.datasets}
height={110}
width={100}
/>
}
loader can go from true to false or vise versa, depending on what make more intuitive sense to you.

Populate React Table with firebase realtime database data

I want to populate my table which i build using react library (react-table-6) with firebase data (using realtime database). I am getting values in console but not being able to put in table, each value in its own field. Values are rendering but i know im doing some silly mistake here.
See this image to see screen
Can anybody explain what im doing wrong here,
Below dropping function through which im retrieving values..
State:
this.state = {
data: [ {trainerName: '', CourseName: '', evidence: '', comment: ''}
]}
function:
get_course_list(){
return firebase.database().ref('Users/CourseApprovals/').on('value', (snapshot) => {
var data = [];
snapshot.forEach((childSnapshot) => {
var childData= childSnapshot.val();
var child1 = childData.comments;
var child2 = childData.evidence;
var child3 = childData.selectedTrainer.label;
var child4 = childData.selectedTrainer.value;
var CompleteData = {child1, child2, child3, child4};
data.push({
data: CompleteData
});
})
this.setState({
data
}, console.log(data))
})
}
componentDidMount(){
this.get_course_list();
}
And in render,
<ReactTable
data={data}
columns={[
{ Header: "SL No", maxWidth: 100,filterable: false, Cell: props => {
return <div>{props.index + 1}</div>;
}},
{ Header: "Trainer Name", accessor: "trainerName", className: "sticky", headerClassName: "sticky" },
{ Header: 'Course Name', accessor: 'CourseName'},
{ Header: "Evidence", accessor: "evidence" },
{ Header: 'Comments', accessor: 'comment'},
]}
defaultPageSize={10}
className="-striped -highlight"
/>
The problem may be in pushing data twice into the data array. Try this:
get_course_list() {
let data = [];
firebase.database().ref('Users/CourseApprovals').on('value', snapshot => {
if (snapshot.exists()) {
// making sure data exists
snapshot.forEach(child => {
let a = child.val();
// build the object
let CompleteData = {
child1: a.comments,
child2: a.evidence,
child3: a.selectedTrainer.label,
child4: a.selectedTrainer.value
}
// you are currently doing: data.push({ data: CompleteData })
// by doing so your data array looks like this:
// data:[{ data: { child1: '', ... } }, ...]
data.push(CompleteData)
// now your array should look like this:
// data:[{ child1: '', ... }, ...]
});
// setState
this.setState({ data });
console.log(data);
}
})
}
componentDidMount() {
this.get_course_list();
}

Chartjs populate data with Axios response

I am attempting to have the data on the chart populate based on the set of data the user selects i.e past 24-hours, past week, etc. I am saving the data and the labels in state. The labels update according to the selected time frame, but none of the data populates. I have console logged the data (this.state.data.datasets[0].data[0]) and it is the correct data.
Here is my code:
class ChartDemo extends Component {
state = {
target: 20,
timeFrame: "past-hours",
totalSales: [],
data: {
labels: [],
datasets: [
{
label: "",
backgroundColor: "",
// data results
data: []
}
]
},
chartIsLoaded: false,
}
getData = (start, end) => {
API.getData(start, end)
.then(res =>
this.setState(state => {
// if any sales have occured in selected time period
if (res.data[0] !== undefined) {
let total = res.data[0].profit.toFixed(2);
let totalString = total.toString();
const totalSales = state.totalSales.concat(totalString);
return {
totalSales
};
} else {
// set to zero if no sales
const noSale = "0.00";
const totalSales = state.totalSales.concat(noSale);
return {
totalSales
};
}
})
)
.catch(error => console.log( error));
}
UNSAFE_componentWillMount() {
this.setTimeFrame();
}
setTimeFrame() {
const day-one = 2019-08-01;
const day-two = 2019-08-02;
const timeFrame = this.state.timeFrame;
this.setState({ target: 20 });
if (timeFrame === "past-hours") {
this.getData(day-one, day-two);
if (this.state.totalSales.length < 8) {
this.setState({ target: 7, chartIsLoaded: true });
setTimeout(
function () {
this.setState(prevState => ({
data: {
...prevState.data,
labels: [
timeset-one,
timeset-two,
timeset-three,
timeset-four,
timeset-five,
timeset-six,
timeset-seven,
timeset-eight,
],
datasets: [{
...prevState.data.datasets,
label: "24-hour Profit in $",
backgroundColor: "rgb(1,41,95)",
data: [this.state.totalSales]
}]
}
}))
}.bind(this), 1000
)
}
}
}
I solved this by removed the [] around this.state.totalSales. I was essentially putting an array into another array.

Resources