Modify data in React Google Chart - javascript

i'm learning react and i getted API data but i don't know how can i display in React Google Charts.
The format to show in React Google Charts is like this:
['Germany', 200,0],
['United States', 300,0],
['Brazil', 400,0],
['Canada', 500,0],
['France', 600,0],
['RU', 700,0],
This is my code:
import React, { useState, useEffect } from "react";
import Chart from "react-google-charts";
const Map = ({ url }) => {
const [stats, setStats] = useState(null);
const [values, setValues] = useState([]);
const getData = async () => {
const data = await fetch(url);
const json = await data.json();
setStats(json);
for (const key in json) {
values.push([json[key].country, json[key].active, json[key].deaths]);
}
console.log(values);
// ["Afghanistan", 5717,169] --> I receive the data perfectly but i don't know how can i display it below
};
useEffect(() => {
getData();
}, []);
return (
<div className="col-xl-12 text-center mb-3">
<h4>Hover on map to see...</h4>
<Chart
width={"100%"}
height={"350px"}
chartType="GeoChart"
options={{
displayMode: "regions",
backgroundColor: "#81d4fa",
colorAxis: {
values: [1000, 10000, 50000, 100000, 1000000],
colors: ["#00bc8c", "#f39c12", "#e74c3c", "red", "darkred"],
},
}}
data={[
["Country", "Active cases", "Deaths"],
values.map(value => value + ",") //i tried this...
]}
// Note: you will need to get a mapsApiKey for your project.
// See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings
mapsApiKey="AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWY"
rootProps={{ "data-testid": "1" }}
/>
</div>
);
};
export default Map;
I commented the line with my fail tries.
I tried a lot of things but i can't show the data received
Thanks for your time

You need to run setValues after you load your json data. The changes you make to values don't take effect until you run setValues.
Also, although values feels like a list, perhaps consider avoiding using it directly with .push or similar. Instead, create a local copy of its current state, and then run setValues when you have the data for the map:
useEffect(() => {
const getData = async () => {
fetch(url)
.then(data => data.json())
.then(json => {
setStats(json);
let myVals = [];
for (let key in json) {
const { country, active, deaths } = json[key];
myVals.push([country, active, deaths]);
}
setValues(myVals);
});
};
getData();
}, []);
But if you're just reassigning that data with the same order then there could be a way to simplify it with map, something along the lines of:
const newValues = json.map(key => {
const { country, active, deaths } = json[key];
return [country, active, deaths];
});
setValues(newValues);
And you could safely condense that into one line, doesn't use push etc.
I also needed to make it only render the <Chart> element after the data was loaded, so I'm using a separate state variable for that, along the lines of:
[dataLoaded, setDataLoaded] = useState(false),
...
setValues(newValues);
setDataLoaded(true);
...
return <div>{
dataLoaded ?
<Chart
chartType="..."
data={[
['Country', 'Active', 'Deaths'],
...values
]} /> :
null}
</div>;
You could maybe just use the length of values or similar as a flag to refactor it, rather than a separate variable altogether.
I also ended up putting the column headers into the state variable as well, so that the data attribute for the <Chart> is just the name of the variable. Mine is:
[dataLoaded, setDataLoaded] = useState(false),
[googleData, setGoogleData] = useState([]),
...
const googleFormattedData = dataReturnedToGoogleData(0, data);
let va = googleData;
va[j] = googleFormattedData;
setGoogleData(va);
setDataLoaded(true);
...
(within a loop)
<Chart
data={
googleData[data.optionNumber]
} />
Not running setValues is the main issue with your version.
Personally I'd avoid using the word json as a variable name, even though it's not reserved when it's lowercase.

Related

Can't update array state using information from another state

I have two state objects. One is personnel, an array of 1-object arrays like this: [[{}],[{}],[{}],[{}],...]. Another is rowItems which I am trying to fill by pulling out all the objects from the inner arrays of the big personnelarray.
My end goal is to use the rowItems to create a material-ui data grid. Right now, the data grid is empty and not rendering any data, but shows the correct number of personnel items I expect (253) in the pagination display, which is weird.
Here's my code:
const [personnel, setPersonnel] = useState([]);
const [rowItems, setRowItems] = useState([]);
const handleCallback = (data) => {
setPersonnel((prevData) => [...prevData, data]);
};
useEffect (() => {
console.log("personnel:", personnel) // I see all 253 arrays printed
setRowItems((rowItems => [...rowItems, {id: '59686', first_name: 'vbn',}])) // This was for testing only, somehow hardcoding this works
personnel?.map((row) => {
console.log("row", row[0]); // I see the item being printed
setRowItems(rowItems => [...rowItems, row[0]]);
console.log("row items", rowItems) // this is empty. WHYYYY
})
}, [personnel])
return (
<div> // This is where I get personnel items and pass to callback
{props.personnel.edges.map(({ node }) => {
return (
<Personnel
key={node.__id}
personnel={node}
parentCallback={handleCallback}
/>
);
})}
</div>
<DataGrid
columns={cols}
rows={rowItems}
pageSize={12}
/>
)
I took jsN00b's suggestion and tried to move the setRowItems() outside of the map function like so:
useEffect(() => setRowItems(prev => ([ ...prev, ...personnel?.map(row => ({...row[0]}))])), [personnel]);
and it worked! Thanks a million!

Saving api response to State using useState and Axios (React JS)

I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..

Prevent child state resets on props change

I am currently making a Graph component that fetches data from an API, parses the data to be used with a graph library, and then renders the graph. I have all of that working right now, but the issue I am having is with adding the ability to filter. The filtering I am currently doing is done by the parent of the Graph component, which will set the filters prop in the component which is then processed by a useEffect. But this seems causes some portions to re-render and I am trying to prevent. Below is what I have roughly speaking.
Rough example of Parent:
const Parent = (props) => {
const [filters, setFilters] = useState({});
//there are more state values than just this one also cause
//the same problem when their setState is called.
return (
<Graph filters={filters} />
<FilterComponent
onChange={(value) => setFilters(value)}
/>
)
}
export default Parent
Rough example of Child:
const Graph = (props) => {
const [nodes, setNodes] = useState({});
const [links, setLinks] = useState({});
const [velocity, setVelocity] = useState(0.08);
const createGraph = async () => {
//fetches the data, processes it and then returns it.
//not including this code as it isn't the problem
return {
nodes: nodes,
links: links,
};
}
//loads the graph data on mount
useEffect(() => {
const loadGraph = async () => {
const data = await createGraph();
setNodes(data.nodes);
setLinks(data.links);
};
loadGraph();
}, []);
//filters the graph on props change
useEffect(() => {
//this function uses setNodes/setLinks to update the graph data
filterGraph(props.filter);
}, [props.filters]);
return (
<ForceGraph2D
graphData={{
nodes: nodes,
links: links,
}}
d3VelocityDecay={velocity}
cooldownTicks={300}
onEngineStop={() => setVelocity(1)}
/>
);
}
export default Graph
My main issue is that whenever the FilterComponent updates, while I want it to update the graph data, this seems to re-render the Graph component. This causes the graph to start moving. This graph library creates a graph which kinda explodes out and then settles. The graph has a cooldown of 300, and after which it isn't supposed to move, which is where onEngineStop's function is called. But changing the filter state in Parent causes the graph to regain it's starting velocity and explode out again. I want to be able to change the filter state, update the graph data, without re-rendering it. I've looked into useMemo, but don't know if that's what I should do.
I'm fairly new to React having just started two weeks ago, so any help is greatly appreciated! Also, this is my first post on stackOverflow, so I apologize if I didn't follow some community standards.
Edit
I was asked to include the filterGraph function. The function actually was designed to handle different attributes to filter by. Each node/link has attributes attached to them like "weight" or "size". The filterComponent would then pass the attr and the value range to filter by. If a component falls outside that range it becomes transparent.
const Graph = (props) => {
...
//attr could be something like "weight"
//val could be something like [5,10]
const filterGraph = ({ attr, val }) => {
for (const [id, node] of Object.entries(nodes)) {
const value = nodes[id][attr];
if (val.length == 2) {
if (val[0] > value || val[1] < value) {
const color = nodes[id]["color"] || "#2d94adff";
nodes[id]["color"] = setOpacity(color, 0)
);
} else {
const color = nodes[id]["color"] || "#2d94adff";
nodes[id]["color"] = setOpacity(color, 1)
);
}
}
}
setNodes(Object.values(this.nodes));
}
...
}
In your example you mention that filterGraph uses setNodes/setLinks. So everytime the filter changes (props.filters) you will do 2 setState and 2 rerenders will be triggered. It can be that React will batch them so it will only be 1 rerender.
Depending on what filterGraph does exactly you could consider let it return filteredNodes en filteredLinks without putting the filterGraph in a useEffect.
Then pass the filteredNodes en filteredLinks to the graphData like graphData={{
nodes: filteredNodes,
links: filteredLinks,
}}
This way you won't trigger extra rerenders and the data will be filtered on every render. which is already triggered when the props.filters change. This is an interesting article about deriving state https://kentcdodds.com/blog/dont-sync-state-derive-it
Since you also mention that there are more state values in the parent you could make the component a pure component, which means it won't get rerendered when the parent renders but the props that are being passed don't change
https://reactjs.org/docs/react-api.html#reactmemo
Also it's better to include createGraph in the useEffect it's being used or wrap it in a useCallback so it won't be recreated every render.
const Graph = React.memo((props) => {
const [nodes, setNodes] = useState({});
const [links, setLinks] = useState({});
const [velocity, setVelocity] = useState(0.08);
//loads the graph data on mount
useEffect(() => {
const createGraph = async () => {
//fetches the data, processes it and then returns it.
//not including this code as it isn't the problem
return {
nodes: nodes,
links: links,
};
}
const loadGraph = async () => {
const data = await createGraph();
setNodes(data.nodes);
setLinks(data.links);
};
loadGraph();
}, []);
const { filteredNodes, filteredLinks } = filterGraph(props.filter)
return (
<ForceGraph2D
graphData={{
nodes: filteredNodes,
links: filteredLinks,
}}
d3VelocityDecay={velocity}
cooldownTicks={300}
onEngineStop={() => setVelocity(1)}
/>
);
})
export default Graph

Map object with URL to return object

I loop through this array like this:
{props.choosenMovie.characters.map((characters) => (
<p>{characters}</p> /* This displays the URL of course */
))}
These URL's include a name object which is what i want to display,
what is the best practice to do this?
This is how it is displayed on my application, but the desire is to display the name object from the URL's.
In useEffect, map thru your array of urls and make the api call and store the promises in an array. Use promise.all and update the state which will cause re-render.
In render method map thru the updated state and display the names.
see working demo
Code snippet
export default function App() {
const [char, setChar] = useState([
"https://swapi.dev/api/people/1/",
"https://swapi.dev/api/people/2/"
]);
const [people, setPeople] = useState([]);
useEffect(() => {
const promiseArray = [];
char.forEach(c => {
promiseArray.push(fetch(c).then(res => res.json()));
Promise.all(promiseArray).then(res => {
console.log("res", res);
setPeople(res);
});
});
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{people.map((p, i) => {
return <p key={i}>{p.name}</p>;
})}
</div>
);
}
I was working with that API some time ago, and the way I approached it (to display the names etc) was with Promise.all
so the snipped looked like
axios.get(`https://swapi.dev/api/${this.props.match.path.split('/')[1]}/${this.props.match.params.id}/`).then((res) => {
let characters = []
// get all characters in the movie data
let characterPromises = []
res.data.characters.forEach((character) => {
characterPromises.push(axios.get(character))
})
// Create list with all characters names and link to page
Promise.all(characterPromises).then((res) => {
res.forEach((character, i) => {
characters.push(<li key={i}><Link to={`/${character.data.url.split('api/')[1]}`}>{character.data.name}</Link></li>)
})
this.setState({
characters
})
})
})
}
then I just used the characters lists (from state) in the render method

looping through json data in react hooks state

I wanna display the data that I have in my state in a map I managed to get both latitude and longitude in my state the problem is whenever try to map trough the state I always get it's not a function error here's parts of my code and the data that I have in the state:
const [countriesData, setCountriesData] = useState({});
useEffect(() => {
const fetchAPI = async () => {
setCountriesData(await fetchDataCountries());
};
fetchAPI();
}, [setCountriesData]);
console.log(countriesData);
and mapping through it like this:
{countriesData.map((data)=>(
<Marker latitude={data.countriesData.latitude}
longitude={data.countriesData.longitude}/>
))}
the fetch api function:
export const fetchDataCountries = async () => {
try {
const data = await axios.get(url);
const newData = data.data;
const modfiedData = newData.map((countryData) => {
return countryData.coordinates;
});
return modfiedData;
} catch (error) {
console.log(error);
}
};
For your map to work you need to convert yourstate to an array, now it is an object.
After that, it could look something like this:
{countriesData.map((country) => (
<Marker
latitude={country.latitude}
longitude={country.longitude}
/>
))}
or
{countriesData.map(({ latitude, longitude }) => (
<Marker
latitude={latitude}
longitude={longitude}
/>
))}
But for a more accurate answer, it would be nice to have an example of the contents of your state.
You're initializing the countriesData with an empty object, then you're fetching a API data.
Assuming the response is an array of objects like this:
[
{ latitude: 38.8451, longitude: -37.0294 }
{ ... // other coordinates }
]
Change the initial state to be an empty array like this:
const [countriesData, setCountriesData] = useState([]);
Also, update this block:
{countriesData.map((data) => <Marker latitude={data.latitude} longitude={data.longitude} />)}
Explanation: the map() method only works with Arrays [] and not with Objects {}, therefore when the component was mounted/initialized for the first time, the map method was executed against an object, thus throwing the map is not a function error.

Categories

Resources