new to react so I am not quite sure what I am doing wrong here... I am trying to call data from an API, then use this data to populate a charts.js based component. When I cmd + s, the API data is called in the console, but if I refresh I get 'Undefined'.
I know I am missing some key understanding about the useEffect hook here, but i just cant figure it out? All I want is to be able to access the array data in my component, so I can push the required values to an array... ive commented out my attempt at the for loop too..
Any advice would be greatly appreciated! My not so functional code below:
import React, {useState, useEffect} from 'react'
import {Pie} from 'react-chartjs-2'
const Piegraph = () => {
const [chartData, setChartData] = useState();
const [apiValue, setApiValue] = useState();
useEffect(async() => {
const response = await fetch('https://api.spacexdata.com/v4/launches/past');
const data = await response.json();
const item = data.results;
setApiValue(item);
chart();
},[]);
const chart = () => {
console.log(apiValue);
const success = [];
const failure = [];
// for(var i = 0; i < apiValue.length; i++){
// if(apiValue[i].success === true){
// success.push("success");
// } else if (apiValue[i].success === false){
// failure.push("failure");
// }
// }
var chartSuccess = success.length;
var chartFail = failure.length;
setChartData({
labels: ['Success', 'Fail'],
datasets: [
{
label: 'Space X Launch Statistics',
data: [chartSuccess, chartFail],
backgroundColor: ['rgba(75,192,192,0.6)'],
borderWidth: 4
}
]
})
}
return (
<div className="chart_item" >
<Pie data={chartData} />
</div>
);
}
export default Piegraph;
There are a couple issues that need sorting out here. First, you can't pass an async function directly to the useEffect hook. Instead, define the async function inside the hook's callback and call it immediately.
Next, chartData is entirely derived from the apiCall, so you can make that derived rather than being its own state variable.
import React, { useState, useEffect } from "react";
import { Pie } from "react-chartjs-2";
const Piegraph = () => {
const [apiValue, setApiValue] = useState([]);
useEffect(() => {
async function loadData() {
const response = await fetch(
"https://api.spacexdata.com/v4/launches/past"
);
const data = await response.json();
const item = data.results;
setApiValue(item);
}
loadData();
}, []);
const success = apiValue.filter((v) => v.success);
const failure = apiValue.filter((v) => !v.success);
const chartSuccess = success.length;
const chartFail = failure.length;
const chartData = {
labels: ["Success", "Fail"],
datasets: [
{
label: "Space X Launch Statistics",
data: [chartSuccess, chartFail],
backgroundColor: ["rgba(75,192,192,0.6)"],
borderWidth: 4,
},
],
};
return (
<div className="chart_item">
<Pie data={chartData} />
</div>
);
};
export default Piegraph;
pull your chart algorithm outside or send item in. Like this
useEffect(async() => {
...
// everything is good here
chart(item)
})
you might wonder why I need to send it in. Because inside useEffect, your apiValue isn't updated to the new value yet.
And if you put the console.log outside of chart().
console.log(apiData)
const chart = () => {
}
you'll get the value to be latest :) amazing ?
A quick explanation is that, the Piegraph is called whenever a state is updated. But this update happens a bit late in the next cycle. So the value won't be latest within useEffect.
Related
I want to make hook to get data from Snapshot to display proposals. I use graphql-request library to get data. I want to get this data in component for example: const { data } = useSnapshotProposalsQuery(). How can i do this? For now i can only get const data = useSnapshotProposalsQuery() and when i am console.log(data) i get Promise{<opening>}. My code:
import { gql, request } from 'graphql-request';
export const useSnapshotProposals = gql`
query Proposals {
proposals(
first: 20
skip: 0
where: { space_in: ["example.eth"] }
orderBy: "created"
orderDirection: desc
) {
id
title
body
choices
start
end
snapshot
state
author
space {
id
name
}
}
}
`;
export const useSnapshotProposalsQuery = () => {
return request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => data);
};
You create a custom hook. and that hook returns a state. when sideeffect inside that hook happens, the state is updated and your outer component gets re-rendered. (react docs)
export const useSnapshotProposalsQuery = () => {
const [myData, setMyData] = useState(null);
useEffect(()=>{
request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => {setMyData(data)});
}, []); // run only one time
return myData;
};
in outer component:
function ABCcomponent () {
const myData = useSnapshotProposalsQuery(); // it will be null at first, but will be filled with data later.
return (
/*ui that uses myData */
)
}
I'm trying to build a simple web app that displaying values in real-time.
So the code I was built is below.
import {useEffect, useState, VFC} from "react";
import {Heading} from "#chakra-ui/react";
import {useSocket} from "../../context/socketContext";
import {chartDataType} from "../../types/chartDataType";
type singleDataPair = {
dataName: string;
unixTime: number;
value: number;
}
//This function just makes a readable date and time string.
const current_time_readable = () => {
let date_obj = new Date();
return `${date_obj.getUTCFullYear()}-${('0' + (date_obj.getUTCMonth() + 1)).slice(-2)}-${('0' + date_obj.getUTCDate()).slice(-2)} ${('0' + date_obj.getUTCHours()).slice(-2)}:${('0' + date_obj.getUTCMinutes()).slice(-2)}:${('0' + date_obj.getUTCSeconds()).slice(-2)}`;
};
type Props = {
dataName: string;
};
export const ChartCard: VFC<Props> = (props) => {
const {dataName} = props;
const {socket} = useSocket();
let singleDataPair: Array<singleDataPair> = [];
const [chartData, setChartData] = useState<Array<singleDataPair>>([]);
//Update an array and setState function.
//This update is deleting its first element and add a value as its last element.
const arrayUpdate = (array: Array<singleDataPair>, value: chartDataType) => {
if (array.length > 1) {
let tempArray = array;
tempArray.shift();
tempArray.push({
dataName: dataName,
unixTime: value['end_time_unix'],
value: value[dataName],
});
setChartData(chartData => tempArray);
console.log('updated');
console.log(chartData);
}
}
//Get initial data and extract single pair of time and data.
socket.on('initial_data_to_browser', ((data) => {
if (data) {
data.forEach((obj: any) => {
singleDataPair.push({
value: obj[dataName],
unixTime: obj['end_time_unix'],
dataName: dataName,
});
});
}
setChartData(chartData => singleDataPair);
console.log('initialData:');
//This displays empty array.
console.log(chartData);
}));
//Get new data every 10 seconds and update the state using arrayUpdate function above.
socket.on('test_emit_10s', ((data) => {
if (singleDataPair) {
arrayUpdate(chartData, data);
}
}));
return (
<>
{
//Display the data on the browser
chartData ? (chartData.map((data: any) => (
<Heading as="h1"
key={data.unixTime}>{`${current_time_readable()} | ${data.unixTime} | ${data.value}`}</Heading>
))) : null
}
</>
)
}
The above code just executes get initial data the time getting initial_data_to_browser event at first then getting update data constantly the time getting test_emit_10s event.
But this code does not work properly.
I can not figure out the exact usage of setState of react in this case. This code has two problems below.
Problem1(regarding displaying the initial data)
The code above can display initial data the time getting the initial_data_to_browser event on the browser.
On the other hand, the result of the console.log inside the callback of this event is an empty array. Why?
Problem2(regarding updating the data and displaying it)
The time getting test_emit_10s event, it can not update the browser display.
On the other hand, the result of console.log inside the callback of this event can be updated. Why?
In the end, the result of console.log and the display of the browser does not correspond.
In my idea, the reason for this problem is my usage of setState.
But I can not come up with a solution for these problems.
Anyone who can help me?
Just in case, the socketContext provider code is below. This simply provides a socket object.
import { useContext, createContext } from "react";
import {io, Socket} from "socket.io-client";
type Context = {
socket: Socket,
}
const socket: Socket = io("http://***.***.***.***");
const SocketContext = createContext<Context>({
socket,
});
const SocketProvider = (props: any) => {
return (
<SocketContext.Provider value={{ socket }} {...props} />
);
}
const useSocket = () => useContext(SocketContext);
export { SocketProvider, useSocket }
I do the weather app and need some help. In component Chart in options and series comes [object Object]. When you change something in the code, it is displayed. I think that the problem with useEffect? but I don't know how to fix that
import React, { useContext, useState, useEffect } from 'react';
import Chart from 'react-apexcharts';
import { Context } from '../../contex';
const WeatherGrapth = () => {
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, []);
return(
<div>
<Chart options={{
chart: {
id: 'weather-graph'
},
xaxis: {
categories: category,
title: {
text: 'Date',
},
},
yaxis: {
title: {
text: 'Temperature °C',
},
},
}}
series={[{
name: 'temp',
data: data
}]} type="line" height={'349px'} />
</div>
)
}
export default WeatherGrapth;
But as soon as I change something in the code, everything will update and a graph will appear.
As React doc says:
By default, effect runs both after the first render and after every update
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Probably At first dailyForecast context is empty or has not any valid data and after that it fills with data you should pass it to useEffect as dependency to run the effect at changes:
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, [dailyForecast]);
I want to use UseState hook for updating data in my Table component. The data to be used in the Table component is fetched by another function which is imported paginationForDataAdded.
Its look like stackoverflow due to re-rendering.
setAllData(searchResults); will re-render the component and again make api call and repated.
right way to call API.
const [allData, setAllData] = useState([]);
useEffect(function () {
const {
searchResults,
furnishedData,
entitledData
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
setAllData(searchResults);
});
Assuming paginationForDataAdded is a function that returns a Promise which resolves with an object that looks like the following:
{
searchResults: { resultarray: [...] },
furnishedData: [...],
entitledData: [...]
}
You should do the following your in component:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
paginationForDataAdded({
searchFunction: search,
collectionsData: collections,
})
.then(
({ searchResults, furnishedData, entitledData }) => {
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData);
}
)
.catch(/* handle errors appropriately */);
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
However, if paginationForDataAdded is not an asynchronous call, then you should do the following:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
const {
searchResults,
furnishedData,
entitledData,
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData)
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
Hope this helps.
Having a state that changes after a while (after fetching content), if I want to construct a variable and build a new state from that, when the first state changes it is not propagated to the second state
How can I solve this? I wouldn't like to merge the 2 states into one since this would mix components that do different things
If you have time you can take a look at the codesandbox below
https://codesandbox.io/s/purple-sun-mx428?fontsize=14&hidenavigation=1&theme=dark
And I paste here the code
import React, { useEffect, useState } from "react";
import "./styles.css";
const wait = ms => new Promise((r, j) => setTimeout(r, ms));
const test2 = () => {
const [test, setTest] = useState("hi");
useEffect(() => {
const testf = async stillMounted => {
await wait(1000);
setTest("bye");
};
const stillMounted = { value: true };
testf(stillMounted);
return () => {
stillMounted.value = false;
};
}, []);
return test;
};
export default function App() {
const here = test2();
const stru = [{ id: "whatever", content: here }];
const [elements, setElements] = useState(stru);
console.log(stru);
console.log(elements);
return (
<div className="App">
<h1>stru: {stru[0].content}</h1>
<h2>elements: {elements[0].content}</h2>
</div>
);
}
You can write an effect that runs when the here variable changes. Add something like this to your App component.
useEffect(() => {
// This runs when 'here' changes and then updates your other state
setElements([{ id: "whatever", content: here }])
}, [here]) // This makes this effect dependent on the 'here' variable