Reputation: 192
I am creating a graph using the react-plotly library. The data from the graph is being queried from an API endpoint.
The service file is shown below
import React from 'react'
export default async function GetStockData(token,ticker,setData, setSuccess) {
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer " + token)
var formdata = new FormData();
formdata.append("Tick", ticker);
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
await fetch("https://abc.azurewebsites.net/api/stocks", requestOptions)
.then(response => response.json())
.then(response => setData(JSON.parse(response.content))
}
I am still the add error handling. I have fixed the ticker to a value that I know works for now.
This service is then called by the component below:
import React, { useState, useContext, useEffect} from 'react'
import GetStockData from '../Services/GetStockData'
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from "../Store/AuthContext";
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false)
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const ticker = 'MSFT'
const clickHandler = async()=>{
await GetStockData(token,ticker,setChartData, setChartSuccess)
const {data, preplayout} = dataPrep()
setpreppedData(data)
setlayout(preplayout)
}
const dataPrep =()=>{
var dateData = []
var closeData = []
var openData = []
var lowData = []
var highData = []
for(var prop in chartData["Time Series (Daily)"]){
dateData.push(prop)
for(var prop2 in chartData["Time Series (Daily)"][prop]){
if (prop2=="1. open"){ openData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="2. high"){ highData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="3. low"){ lowData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="5. adjusted close"){ closeData.push(chartData["Time Series (Daily)"][prop][prop2])}
}
}
var trace1 = {
x: dateData,
close:closeData,
increasing: {line: {color: 'green'}},
decreasing: {line: {color: 'red'}},
high: highData,
line: {color: 'rgba(31,119,180,1)'},
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y'
};
var data = [trace1]
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date'
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear'
}
};
return {data , layout}
} ;
useEffect(() => {
if (preppedData !== null) setIsDataLoaded(true);console.log(preppedData)
}, [preppedData]);
return (
<>
<div>{isDataLoaded?
<Plot
data={preppedData}
layout={layout}>
</Plot>: null}
</div>
<button onClick={()=>clickHandler()}>Refresh</button>
</>
)
}
There are probably a whole host of things that could be improved here. I am using too stateful variables for a start. I currently have a local store of data to use as a sample to prevent an error occurring prior to the request being made. Any comment on how to manage first render when awaiting web based content would be massively appreciated.
My core question relate to setChartData, setChartSuccess. On the press of the button, these are passed to the service file where they are updated simultaneously. However, the chartSuccess variable seems to update prior to ChartData. The jsx conditional triggers and renders the graph but it doesn't contain the latest preppedData. On pressing the button a second time the updated data appears. Am I making an error in the sequencing?
Upvotes: 2
Views: 134
Reputation: 413
From what I see, you can do 2 things to solve this. Either set success state inside the click handler and if the success state can't be moved to elsewhere as it might be used in some other place, you can have a new state variable and use it for the condition for render or not.
I'm showing you how to do it with the 2nd method here, but you can give 1st method a try if you don't have problem I mentioned.
So, your component code will look like this.
import React, { useState, useContext, useEffect } from 'react';
import GetStockData from '../Services/GetStockData';
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from '../Store/AuthContext';
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false);
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const ticker = 'MSFT';
const clickHandler = async () => {
await GetStockData(token, ticker, setChartData, setChartSuccess);
const { data, preplayout } = dataPrep();
setpreppedData(data);
setlayout(preplayout);
setIsDataLoaded(true);
};
const dataPrep = () => {
var dateData = [];
var closeData = [];
var openData = [];
var lowData = [];
var highData = [];
for (var prop in chartData['Time Series (Daily)']) {
dateData.push(prop);
for (var prop2 in chartData['Time Series (Daily)'][prop]) {
if (prop2 == '1. open') {
openData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '2. high') {
highData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '3. low') {
lowData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '5. adjusted close') {
closeData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
}
}
var trace1 = {
x: dateData,
close: closeData,
increasing: { line: { color: 'green' } },
decreasing: { line: { color: 'red' } },
high: highData,
line: { color: 'rgba(31,119,180,1)' },
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y',
};
var data = [trace1];
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60,
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date',
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear',
},
};
return { data, layout };
};
return (
<>
<div>
{isDataLoaded ? <Plot data={preppedData} layout={layout}></Plot> : null}
</div>
<button onClick={() => clickHandler()}>Refresh</button>
</>
);
}
UPDATED as per requirement
import React, { useState, useContext, useEffect } from 'react';
import GetStockData from '../Services/GetStockData';
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from '../Store/AuthContext';
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false);
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const ticker = 'MSFT';
const clickHandler = async () => {
await GetStockData(token, ticker, setChartData, setChartSuccess);
const { data, preplayout } = dataPrep();
setpreppedData(data);
setlayout(preplayout);
};
const dataPrep = () => {
var dateData = [];
var closeData = [];
var openData = [];
var lowData = [];
var highData = [];
for (var prop in chartData['Time Series (Daily)']) {
dateData.push(prop);
for (var prop2 in chartData['Time Series (Daily)'][prop]) {
if (prop2 == '1. open') {
openData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '2. high') {
highData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '3. low') {
lowData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '5. adjusted close') {
closeData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
}
}
var trace1 = {
x: dateData,
close: closeData,
increasing: { line: { color: 'green' } },
decreasing: { line: { color: 'red' } },
high: highData,
line: { color: 'rgba(31,119,180,1)' },
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y',
};
var data = [trace1];
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60,
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date',
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear',
},
};
return { data, layout };
};
useEffect(() => {
if (preppedData !== null) setIsDataLoaded(true);
}, [preppedData]);
return (
<>
<div>
{isDataLoaded ? <Plot data={preppedData} layout={layout}></Plot> : null}
</div>
<button onClick={() => clickHandler()}>Refresh</button>
</>
);
}
Upvotes: 1