Below is the component I am working on:
//PURPOSE : Component to show some useful information about the current page. like LAST REFRESH TIME OF DATA.
//Props that needs to be passed. - {MessageTitle}
//
import React, { useEffect, useState } from "react";
import "../../css/BubbleInfoComponent.scss";
import ApiHelper from "../../api/ApiHelper";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
const BubbleInfoComponent = (props) => {
let [Info, setInfo] = useState({});
function onClicko() {
console.log("Handling Click");
}
useEffect(() => {
getData();
// eslint-disable-next-line
}, [Info]);
function getData() {
ApiHelper.GetLastRefreshTime(props.uid).then((response) => {
// const infoData = response.data.map(({sysName, lastRefreshTime}) =>{
// return (sysName ? <div key={sysName}>`{sysName}:{lastRefreshTime}`</div>): <div>{lastRefreshTime}</div>;
// })
// setInfo(infoData);
// console.log("infoData:- ")
// console.log(infoData);
});
}
return (
<>
<div id="Bubble__Circle" onClick={onClicko}>
<p>
<FontAwesomeIcon icon="info" color="#30343f" />
</p>
</div>
<div id="Bubble__Content">
<div id="Bubble__Content_Msg_Title">{props.MessageTitle}</div>
{/* <div id="Bubble__Content_Msg_Title">{Info}</div> */}
</div>
</>
);
};
export default BubbleInfoComponent;
There will be two kind of JSON response I will be getting:
{
"SystemA": "07-04-2021 08:00",
"SystemB": "07-04-2021 08:00",
"SystemC": "07-04-2021 08:00"
}
{"responses": "07-04-2021 08:00"}
What I want to implement is 1st type of response I want to set the value of Info in "Key": "Value" format and for 2nd type of response only time should be visible.
I hope I made my point clear. I know I did something wrong while destructuring in line21, this might look silly but being a newbie to JavaScript, I am not able to identify where i am going wrong.
Map function can be run on arrays and not objects. So the first thing which has to be done is to convert object into and array. For that you can use the Object.entries which will give you the key and values for each of the elements in the object.
Reference - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Based on the response type you either access the time directly or run through a map. Here is the code to start with :
ApiHelper.GetLastRefreshTime(props.uid).then((response) => {
const mapSystemNames = (data) => Object.entries(data).map(([key,value]) => <div key={key}>{key}:{value}</div>)
const infoData = response.data.responses? <div>{response.data.responses}</div> : <div>{mapSystemNames(response.data)}</div>;
// setInfo(infoData);
// console.log("infoData:- ")
// console.log(infoData);
});
Probably there may be some edge case scenarios / type checks needed to make it more robust, but this could get you started in the right direction. Please let me know if you have any queries.
Related
I'm reusing a couple of external components to create my custom Combobox in strapi app.
Values are received from server so I need to add options dynamically.
Currently there is the following code:
import React, { useState, useEffect } from "react";
import {
Combobox,
ComboboxOption
} from "#strapi/design-system";
export default function ComboboxCustom({
valuesList,
valueSelected
}) {
const [value, setValue] = useState('');
const combo = (<Combobox label="Country" value={value} onChange={setValue}>
{valuesList.map((entry) => {
return(
<ComboboxOption value="{entry.id}">{entry.name}</ComboboxOption>
);
})}
</Combobox>);
// setValue(valueSelected)
return combo;
}
And everything goes good until I try so set 'selected' option basing on another set of data. In static world I could just say useState(valueSelected) and it will work. But as code generated dynamically, there is no related option yet, so I get failure like "Failed to get 'props' property of undefined".
I tried to put this combobox into a variable and set state between creation and returning it (commented setValue line before the return statement) but then app gets in a loop and returns "Too many re-renders".
Does anyone has an idea of how to change/rewrite this to be able to set selected value for dynamically created combobox?
So I assume that the values are dynamically fetched and passed to the ComboboxCustom.
I think you can add setValue(valueSelected) inside an useEffect.
onChange of the prop valueSelected.something like,
useEffect(() => {
setValue(valueSelected)
}, [valueSelected])
Also handle the return when the value is not yet loaded. like before doing valuesList.map, first check if valueList ? (render actual) : (render empty)
Hope this helps!!
Thanks,
Anu
Finally I got working solution based on answer from #Anu.
Cause valuesList is got as GET-request from another hook, I have to check values are already present (first hook hit gives [] yet) and bind Combobox state updating to change of valuesList also. Though I don't fell like this solution is perfect.
import React, { useState, useEffect } from "react";
import {
Combobox,
ComboboxOption
} from "#strapi/design-system";
export default function ComboboxCustom({
valuesList,
valueSelected,
}) {
const [value, setValue] = useState('');
let combo = null;
useEffect(() => {
if(combo && combo?.props?.children?.length > 0 && valuesList.length > 0) {
setValue(valueSelected)
}
}, [valueSelected, valuesList])
combo = (<Combobox label="Country" value={value?.toString()} onChange={setValue}>
{valuesList.map((entry) => {
return(
<ComboboxOption value={entry?.id?.toString()}>{entry.name}</ComboboxOption>
);
})}
</Combobox>);
return combo;
}
After that I decided avoid creating custom component based on already published as I'll need to add and process event listeners that are added for us in the existing components. So I placed this code directly into my modal and it also works:
const [countries, setCountries] = useState([]);
const [deliveryCountryValue, setDeliveryCountryValue] = useState('');
useEffect(async () => {
const countriesReceived = await countryRequests.getAllCountries();
setCountries(countriesReceived);
}, []);
useEffect(() => {
// If there is no selected value yet, set the one we get from order from server
const valueDelivery = deliveryCountryValue != '' ? deliveryCountryValue : order.country?.id;
if(countries.length > 0) {
setDeliveryCountryValue(valueDelivery);
order.country = countries.find(x => x.id == valueDelivery);
}
}, [deliveryCountryValue, countries])
<Combobox key='delivery-combo' label="Country" value={deliveryCountryValue?.toString()} onChange={setDeliveryCountryValue}>
{countries.map((entry) => {
return(
<ComboboxOption key={'delivery'+entry.id} value={entry?.id?.toString()}>{entry.name}</ComboboxOption>
);
})}
</Combobox>
I don't understand why my "console.log(champion)" return nothing ...
Someone can explain me why the asynchrone function don't work ? Isn't setCahmp supposed to change the value of "champions"?
I guess it because axios take sometime to search datas... I don't know how I could fix it.
And then I would like to map "champion" but its an object, how I could do that ?
Thans you
import React, { useEffect, useState } from "react";
import axios from "axios";
const Champs = () => {
const [champions, SetChampions] = useState([]);
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
})
.then(
console.log(champions)
);
}, []);
return (
<div className="champs">
{/* {champions.map((champ) => {
return <p> {champ.id}</p>;
})} */}
</div>
);
};
export default Champs;
In your API response response.data.data is not an array of objects, it's nested objects and you are initializing the champions as an array. So, setChampions can't assign an object to an array.
Also, you can't use the map function to loop an object. You can use Object.keys to map the response.
You shouldn't do a double "then" on your code. If you want to know when the state champions is set you should use a second useEffect with "champions" in param :
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
});
}, []);
useEffect(() => {
console.log(champions)
}, [champions]);
If you want to map an object you should do this :
<div className="champs">
{Object.keys(champions).map((key) => {
const champ = champions[key]
return <p> {champ.id}</p>;
})}
</div>
Object.keys will return an array of key of your object, so you can map it. And to access to the value you can simply use the key like this : const champ = champions[key]
Hoping that can help you in your research
It could be that console.log(champion) isn't working because it's getting called before SetChampion is completed. You don't need the 2nd .then() call to check on champion. To make sure champion is getting set, you could make a useEffect that is called whenever champion gets set, like so:
useEffect(() => {
console.log(champion);
}, [champion])
This will get called when champion is initially set to [] with useState, and then when you set it with SetChampions().
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..
I am having a bit of an issue when attempting to .map a React state variable into various JSX elements. I have noticed that the state variable I am storing the array within is not undefined, as "console.log-ing" the variable appears to hold the information I need it to. The error according to my React Development Page mentions the error starts at line 43 Here is my code for the main Dashboard.js I am utilizing the .map function within to create new list items.
import React, {useState} from 'react';
import {generateId} from '../../Utilities/generateId.js';
import {expirationTime} from '../../Utilities/expirationTime.js';
import {Form} from '../Form/Form.js';
import {Thought} from '../Thought/Thought.js';
export function Dashboard() {
// Sets up sample message when site first loads up; eventually disappears after 15s
// generateId adheres an id number starting at 0, to each thought to easily identify each thought
// expirationTime sets the automatic removal of the "thought" 15 seconds away from current time
const [thoughts, setThoughts] = useState([
{
id: generateId(),
text: "Add your thoughts, don't be shy!",
expires: expirationTime(),
},
{
id: generateId(),
text: 'They disappear after 15 seconds!',
expires: expirationTime(),
}
]);
//adds new thought object to array lists out all collective thoughts in thoughts array
const addThought = (thought) => {
setThoughts((prev) => [thought, ...prev]);
};
const removeThought = (thoughtID) => {
//reuses state and uses filter function to cycle through each object in the thoughts array,
// and remove the thought whose ID matches the one that was selected(clicked) on
setThoughts((prev) => {
prev.filter((thought) => thought.id !== thoughtID)
});
};
return (
<div>
<h1>Thought Machine</h1>
<h3>Welcome! This is an open space for any of your thoughts.</h3>
<h2>Thanks for visiting!</h2>
<Form addThought={addThought} />
<ul className='thoughts'>
{thoughts.map( thought =>
(<Thought key={thought.id} thought={thought} removeThought={removeThought} />)
// thought.text
)}
</ul>
</div>
)
};
Aside from the main code here, here is my code for Thought.js, where I pass in my props for the "thoughts" I am attempting to individually list out. I do not believe there is any issue with my code here, yet I am posting it just in case.
Thought.js:
import React, {useEffect} from 'react';
export function Thought(props) {
const {thought, removeThought} = props;
useEffect(() => {
const timeRemaining = thought.expirationTime - Date.now();
const timeout = setTimeout(() => {
removeThought(thought.id)
}, timeRemaining);
return () => {
clearTimeout(timeout);
}
}, [thought]);
const handleRemoveClick = () => {
removeThought(thought.id);
};
return (
<li className='Thought'>
<button className='remove-button' onClick={handleRemoveClick}>×</button>
<p className='text' >{thought.text}</p>
</li>
)
};
I also do not believe that there is any issue with my code for the Form, yet here is that following code just in case my judgment is incorrect.
Form.js:
import React, {useState} from 'react';
import {generateId} from '../../Utilities/generateId.js';
import {expirationTime} from '../../Utilities/expirationTime.js';
import './Form.css';
export function Form(props) {
const [text, setText] = useState('');
//this function is called everytime the text value in the entry box is changed... adjusts state value of text
const handleTextChange = (event) => {
setText(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
//sets object for new thought and fills in new message; new generated ID and new time 15 seconds ahead of current time
const thought = {
id: generateId(),
text: text,
expiresAt: expirationTime(),
};
// checks if text has any text within it, then submits the new 'thought' to props.addThought
if (text.length > 0) {
props.addThought(thought);
// sets text state back to blank after submitting new thought
setText('');
}
}
return (
<form onSubmit={handleSubmit} className='Form'>
<input value={text} onChange={handleTextChange} type='text' placeholder="Go ahead ... there's no CIA agents here :)" />
<input type='submit' value='add' />
</form>
);
};
Apologies for the convoluted-ness of the question and thanks ahead of time for any assistance!
May be you are getting an error undefined at line 43 at the time of updating because of this code
const removeThought = (thoughtID) => {
setThoughts((prev) => {
return prev.filter((thought) => thought.id !== thoughtID) <---- You have to return as well
});
};
Explanation
When you are setting state while updating, you are returning nothing, thus the state is settled to undefined and we can't map over undefined.
http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json
This is the api im attempting to work with. For some reason when i go to map over it to pull data from it and display the data it says champions.map is not a function. (I am guessing it says this because i cant iterate over an object using .map. So i tried using Object.keys(champions) and i still cant really get it to do what i want it to do.
I feel im missing something super easy as its been quite some time since i have made any api calls.
Here is my code.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import ChampionContainer from "./ChampionContainer";
// import { Link } from 'react-router-dom';
const ChampionList = () => {
const [ champions, setChampions ] = useState([])
console.log("This is our state",champions)
useEffect(() => {
const url = "http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json";
axios.get(url)
.then(response => {
setChampions(response.data.data);
})
.catch(error => {
console.log("server Error", error)
})
}, [champions]);
return (
// Object.keys(a).map(function(keyName, keyIndex) {
// // use keyName to get current key's name
// // and a[keyName] to get its value
// })
<div>
{Object.keys(champions).map((key) => {
return (<div key={key}>{champions[key]}</div>)
})}
{/* {champions.map((champion) => (
<ChampionContainer key={champion.id} champion={champion}/>
))} */}
</div>
);
}
export default ChampionList;
Thanks in advance for the help.
You are right, you're first attempt at doing .map() on champions wont work as it is not an array, it's an object.
You're second attempt is closer, though when you do -
champions[key]
this returns one of your champion objects. You just need to access the property you want from that object, e.g.
champions[key].name - to get the name of the champion e.g. Aatrox
You have a infinite loop there.
In useEffect pass champions as a parameter, when it changes you excecute useEffect, but in there you setChampions, then champions changes an useEffect is running again. infinite loop as a result.
You debug your program to make sure that respomse.data.data have the information you need?
In setChampions function is better to use setChampions(current => current + newData);
this is an object, so you should use spread operator to map onto a new object.