React pass API data to chart - reactjs

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}
/>
);

Related

How do i stop a dependency from re-rendering infinite times in a useEffect?

I have a react-select multiselect component which is required to have preselected values based on the user role. The react-select component is used to filter data in a react-table.
I have 2 user roles - Dev and tester.
If it the dev, I need to have open and Reopened issues to be filtered on load
If it is a tester, I need to have resolved issues on load
This is a part of the code that i am using to achieve preselected
async function loadInfo() {
const body = {
project_id: projDetails.id,
};
const response = await axios({
method: "post",
url: apilist.dropdownData,
data: body,
})
.catch((err) => console.log(err));
if (response) {
const getData = response.data.data;
// console.log("IsGeneralInfo:", getData)
setGeneralInfo(getData);
let filteredenv = getData.status.filter((item) => item.id == 8 || item.id == 6)
let envfiltered = filteredenv.map((k) => {
return ({ label: k.values, value: k.values });
})
// console.log("envfilter", envfiltered);
// handleMultiStatusFilter(envfiltered);
}
}
// const {current:myArray}=useRef([{ label: 'Closed', value: 'Closed' }])
useEffect(() => {
if(envfilter){
let myArray=[{ label: 'Closed', value: 'Closed' },{ label: 'Reopen', value: 'Reopen' }];
handleMultiStatusFilter(myArray);
}
}, [selectedOptions])
const handleStatusFilter = (e) => {
setFilterValue(e);
if (e.length > 0) {
dispatch(filterByValue({ type: e, viewIssue: viewIssue, }))
}
else {
dispatch(showAllStatus({ type: 'All', viewIssue: viewIssue, }))
}
}
const handleMultiStatusFilter = (e) => {
setFiltered([])
let arr = []
e.map((data) => {
setFiltered(prevState => [...prevState, data.value]);
arr.push(data.value);
})
setSelectedOptions(e)
handleStatusFilter(arr)
}
This is a part of the redux code used for filtering
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchIssueList.fulfilled, (state, action) => {
// Add user to the state array
state.issuesList = {
status: 'idle',
data: action.payload.data.data !== undefined ? action.payload.data.data : [],
dataContainer: action.payload.data.data !== undefined ? action.payload.data.data : [],
no_of_records: action.payload.data.data !== undefined ? action.payload.data.data.length : 0,
error: {}
}
})
The code works fine with the filtering once i login, but the rerendering keeps going to infinite loop
Is there any way i could stop the infinite rerendering of code and have the filtering happen on load of the screen?
while working with dependencies in useEffect, try to use the most primitive part you can find. no complex objects, as they change way too fast.
for example: use the length of an array not the array itself.
even though for arrays it's mostly safe to use itself.
sorry. correction: for arrays it's not safe either. complex objects are compared by reference not by value. for that you need primitive types like number, string or boolean.

How can I stop a react-query useQuery running on a refetchInterval when a condition is met?

I have a requirement to keep fetching data until a condition is met
I can set up the fetch on an interval with the snippet below but I can't figure out how to stop the fetchData when needed. Is this possible?
const { status: answersStatus, data: answers } = useQuery(
['fetchData', { context, activity, stage, country }],
fetchData,
{refetchInterval: 2000});
update:
since react-query v3.25.0, refetchInterval can accept a function to accommodate this use-case better:
refetchInterval: data => (isConditionMet(data) ? false : 2000)
original answer:
with a local state:
const [refetchInterval, setRefetchInterval] = React.useState(2000)
const { status: answersStatus, data: answers } = useQuery(
['fetchData', { context, activity, stage, country }],
fetchData,
{refetchInterval});
you can set the refetchInterval to false or 0 to turn it off. If you want to do it depending on the response, the onSuccess, onError or onSettled callbacks are likely the best place:
const [refetchInterval, setRefetchInterval] = React.useState(2000)
const { status: answersStatus, data: answers } = useQuery(
['fetchData', { context, activity, stage, country }],
fetchData,
{
refetchInterval,
onError: () => setRefetchInterval(0)
});
For me, it was causing infinite loop until passing function to the refetchInterval like so:
refetchInterval: data => (checkMyResponseHere(data) ? false : 2000)
P.S. react-query v.3.38.0
In the end I leveraged short-circuit evaluation so my call is now
const { data: answers } = useQuery(
isThereSomethingToGet && [`fetchData`, { ...qmfRouteParams, context: appContext }],
fetchData,
{
refetchIntervalInBackground: true,
refetchInterval: DocusignPolling.INTERVAL,
},
);
When isThereSomethingToGet is falsy the function isn't called.

Storing images' URLs from firebase storage into firebase database - URLs are getting lost in cyberspace

It's a React app with Redux. A form collects a new recipe data from user. The data may include an array of file objects (images) selected by user for upload. I call it imagesToAdd. An action is called startAddRecipe. The following needs to happen:
Write new recipe to firebase database - this returns a ref.key needed for storing images in storage. - this works ok
If there are imagesToAdd a function uploadImages is called that uploads images to firebase storage and returns an array with uploaded images URLs. this works ok
Now the recipe created in (1.) above needs to be updated with the URLs obtained in (2.) - this does NOT work. console.logs are showing the URLs alright but they are not appearing in firebase database:
images
imageNames
0: "voteUpSmiley.png"
(no imageUrls)
...nor in the Redux store:
images: {
imageNames: [
'voteUpSmiley.png'
],
imageUrls: []
},
Oddly the redux-logger tool shows the data ok in console:
images:
imageNames: ["voteUpSmiley.png"]
imageUrls: ["https://firebasestorage.googleapis.com/v0/b/mniam-…=media&token=0e9b7991-0314-4f24-a94a-6b24f93baed7"]
The function uploadImages contains asynchronous tasks - firebase storage upload and a call for URLs so I await for the result and am getting correct one but it's not passed in time to subsequent statements because as said before the firebase database and the redux store are not getting the URLs. I've been looking at this for 2 days and seem to be going in circles.
I include the relevant code below for good people caring to have a look at it. Thank you.
export const startAddRecipe = (recipeData = {}) => {
return (dispatch, getState) => {
const {
authorEmail = '',
brief= '',
createdAt = 0,
ingredients = { general: [] },
keyWords = [],
preparation = { general: [] },
publishedAt = 0,
shared = false,
tips = '',
title = '',
votes = {
downs: [],
ups: [],
},
imagesToAdd = [],
} = recipeData
let imageNames = []
imagesToAdd.map(image => {
imageNames.push(image.name)
})
let recipe = {
authorEmail,
brief,
createdAt,
images: {
imageNames,
imageUrls: [],
},
ingredients,
keyWords,
preparation,
publishedAt,
shared,
tips,
title,
votes,
}
console.log('recipeB4', recipe); //this is showing URLs even before image upload???
database.ref(`recipes/`).push(recipe).then((ref) => {
console.log('ref.key:', ref.key);
if (imagesToAdd.length > 0) {
(async () => {
recipe.images.imageUrls = await uploadImages(ref.key, imagesToAdd)
console.log('recipeAfterImageUpload', recipe); // URLS are shown here but not reflected in the next line
database.ref(`recipes/${ref.key}`).update(recipe).then(() => {
console.log('RECIPE ADDED & UPDATED');
})
})()
}
dispatch(addRecipe({
id: ref.key,
...recipe,
}))
dispatch(startSetRecipeKeyWords())
})
}
}
const uploadImages = (id, imagesToAdd) => {
let imageUrls = []
imagesToAdd.map(image => {
const uploadTask = storage.ref(`/recipePics/${id}/${image.name}`).put(image)
uploadTask.on('state_changed',
(snapShot) => {
// console.log(snapShot)
},
(err) => {
// console.log(err)
},
() => {
storage.ref(`recipePics/${id}`).child(image.name).getDownloadURL()
.then(fireBaseUrl => {
console.log('fireBaseUrl', fireBaseUrl)
imageUrls.push(fireBaseUrl)
})
})
})
return imageUrls
}

How can I see state within a function? using hooks

I'm trying to update the uploadFiles state inside my updateFile function, when reloading the file, I'm rewriting this component in hooks, but inside the function the state is given as empty.
const [uploadedFiles, setUploadedFiles] = useState({
slides: [],
material: [],
});
const updateFile = useCallback(
(id, data) => {
const value = uploadedFiles.slides.map(uploadedFile => {
return id === uploadedFile.id
? { ...uploadedFile, ...data }
: uploadedFile;
});
console.log('value', value);
console.log('uploadedFilesOnFunction', uploadedFiles);
},
[uploadedFiles]
);
function processUpload(upFile, type) {
const data = new FormData();
data.append('file', upFile.file, upFile.name);
api
.post('dropbox', data, {
onUploadProgress: e => {
const progress = parseInt(Math.round((e.loaded * 100) / e.total), 10);
updateFile(upFile.id, {
progress,
});
},
})
.then(response => {
updateFile(upFile.id, {
uploaded: true,
id: response.data.id,
url: response.data.url,
type,
});
})
.catch(response => {
updateFile(upFile.id, {
error: true,
});
});
}
function handleUpload(files, type) {
const uploaded = files.map(file => ({
file,
id: uniqueId(),
name: file.name,
readableSize: filesize(file.size),
preview: URL.createObjectURL(file),
progress: 0,
uploaded: false,
error: false,
url: null,
type,
}));
setUploadedFiles({
slides: uploadedFiles.slides.concat(uploaded),
});
uploaded.forEach(e => processUpload(e, type));
}
console.log('slides', uploadedFiles);
I expected the state values to be viewed by the function. For me to manipulate and set the state.
There might be other issues, but one thing I've noticed is:
const [uploadedFiles, setUploadedFiles] = useState({
slides: [],
material: [],
});
// A setState CALL FROM THE useState HOOK REPLACES THE STATE WITH THE NEW VALUE
setUploadedFiles({
slides: uploadedFiles.slides.concat(uploaded),
});
From: https://reactjs.org/docs/hooks-state.html
State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
The setState from the useState hook doesn't merge the state. Because it can hold any type of value, not only objects, like we used to do with classes.
From your code you can see that you're erasing some property from state when you're updating like that.
Instead, you should use the functional form of the setState and access the current state prevState, like:
setUploadedFiles((prevState) => {
return({
...prevState,
slides: uploadedFiles.slides.concat(uploaded)
});
});
The updated updateFiles function:
const updateFile = (id, data) => {
setUploadedFiles(prevState => {
const newSlide = prevState.slides.map(slide => {
return id === slide.id ? { ...slide, ...data } : slide;
});
return {
...prevState,
slides: newSlide,
};
});
};

Public API call working on Mozilla but not on Chrome (react-app)

I recently tried to deploy my first react-app on to the web. The website is about looking up details for a certain pokemon and making your own card if you like.
I use Mozilla as my main browser and everything works pretty good. But when I ask for a pokemon request (GET) on chrome I don't get any results. If I have a look at the network console I get a 301 Error (from disk cache). What does this mean? You can look at my website at:
https://daan.boschmans.mtantwerp.eu/
I deployed my app using the npm run build command.
I added the .htaccess file in the public folder with the proper lines.
GET REQUEST:
export const getPokemonSprites = (name) => {
return fetch(`https://pokeapi.co/api/v2/pokemon-form/${name}`).then((response) => {
if(response.statusText === 'OK') {
return response.json();
}
throw new Error('Network response was not ok.');
})
}
export const getPokemonMoves = (name) => {
return fetch(`https://pokeapi.co/api/v2/pokemon/${name}`).then((response) => {
if(response.statusText === 'OK') {
return response.json();
}
throw new Error('Network response was not ok.');
})
}
This I how I handle the GET call:
getPokeData() {
if (this.state.searchValue && this.state.name !== this.state.searchValue) {
this.setState({ isLoading: true, hasError: false, name: "", sprites: [], moves: [], height: "", weight:"", specials: [], base_experience: "", type: [], stats:[], items: [], });
Promise.all([ getPokemonSprites(this.state.searchValue),getPokemonMoves(this.state.searchValue)])
.then( ([spriteList, pokeDetails]) => {
const sprites = Object.values(spriteList.sprites);
const moves = Object.entries(pokeDetails.moves);
const abilities = Object.entries(pokeDetails.abilities);
const types = Object.entries(pokeDetails.types);
const stats = Object.entries(pokeDetails.stats);
for (const [ , value] of Object.entries(moves)) {
this.state.moves.push(value[1].move.name);
}
for (const [, value] of Object.entries(types)) {
this.state.type.push(value[1].type.name);
}
for (const [, value] of Object.entries(abilities)) {
this.state.specials.push(value[1].ability.name);
}
for (const [, value] of Object.entries(stats)) {
let statsValue = `${value[1].stat.name}: ${value[1].base_stat}`;
this.state.stats.push(statsValue);
}
this.setState({sprites, name: spriteList.name, height: pokeDetails.height, weight: pokeDetails.weight, base_experience: pokeDetails.base_experience })
}).then(() => { this.setState({isLoading: false, searchValue: ""})})
.catch(() => { this.setState({isLoading: false, searchValue: "", hasError: true}) })
}
}
Any tips would be really appreciated
Thanks
Firstly, nice site. Looks like a fun little project.
I tried the website on Chrome and it works fine for me.
Looks as though you have a service worker that is caching content. If you used create-react-app, it comes with a service worker built it and looks as though it is caching the content of your API calls in your browser.
I suspect there is an issue with your Chrome's cache. You could try clearing the cache by following this suggestion here or alternatively it may be worth trying to reinstall chrome.

Resources