guys hope everyine is doing well. So I am new to React and attempting to create a Stock APP. This component fetches the stock data for a stock I select from my Stocklist( another component) . I'm using a custom hook and it's functioning properly, but I am getting the cannot read property 'map' of undefined . The Json I am expecting has two arrays , detailed and aggregated and each array contains objects with a date property and a price property. I am attempting to map that data so that I can produce it into a graph.
The problem is the map function is executing before I select this activeStock, so it's trying to map a property of null, even though I put it in a conditional statement so it shouldn't execute
import React from 'react';
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'
import useFetch, { DEFAULT_OPTIONS } from '../shared/useFetch';
const StockGraph = (props) => {
const activeStock = props.activeStock
const string = "stocks/"
const activeURL = string.concat(activeStock, "/price/today")
console.log(activeURL)
const [stocks, isLoading, error] = useFetch(activeURL, DEFAULT_OPTIONS);
if (error) {
return <div>`Error: {error}`</div>;
} else if (isLoading) {
return <div>Loading...</div>;
} else if (stocks != null ) {
const stockdetails= stocks.detailed
const detailed = stockdetails.map((item) => {
return {
x: new Date(item.date),
y: item.price
};
});
const stocksaggr= stocks.aggregated
const aggregated = stocks.aggregated.map((item) => {
return {
x: new Date(item.date),
y: item.price
};
});
const options = {
chart: {
type: 'line'
},
title: {
text: activeStock
},
subtitle: {
text: ''
},
xAxis: { type: 'datetime' },
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: false
}
},
series: [
{
name: 'detailed',
data: detailed
}
,
{
name: 'aggregated',
data: aggregated
}
]
};
console.log('graph-render', stocks)
return (
<section className='stock-graph'>
<div id="stockGraphContainer" className="stock-graph__container">
<HighchartsReact
highcharts={Highcharts}
options={options}
/>
</div>
</section>
)
}
else {
return null;
}
}
export default StockGraph;
Sounds like stocks.detailed or stocks.aggregated itself is undefined. You need to add either add another conditional statement to check for that before executing the map. Or, maybe it shows another underlying problem with your code - should stocks.aggregated/detailed be defined at that point? If so, why isn't it? I can't tell you from what information you've given, unfortunately.
Like what others said, your conditional is only checking if stocks is null. stocks.detailed or stocks.aggregated is what is being mapped so check if those values even exist. Double check if your useFetch() hook is really working as expected and getting the values you need. Throw logs at key points and you'll get closer to pinpointing what is going on.
Related
first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']
I am using the NHL api to try to grab players stats for a given season. I have a utility function with these season values :
export const seasonOptions = [
{ value: "19861987", label: "1986/1987" },
{ value: "19871988", label: "1987/1988" },
{ value: "19881989", label: "1988/1989" },
{ value: "19891990", label: "1989/1990" },
{ value: "19901991", label: "1990/1991" },
{ value: "19911992", label: "1991/1992" },
{ value: "19921993", label: "1992/1993" },
{ value: "19931994", label: "1993/1994" },
{ value: "19941995", label: "1994/1995" },
{ value: "19951996", label: "1995/1996" },
];
... and so on. In my component I have this state to setSelect on what was selected:
const [select, setSelect] = useState(seasonOptions[seasonOptions.length - 1]);
const handler = (selected) => {
setSelect((select) => select);
handlePlayerStats(
`https://statsapi.web.nhl.com/api/v1/people/${props.playerId}/stats?stats=statsSingleSeason&season=${selected.value}`
);
}
};
<Select
id="select"
instanceId={"select"}
options={seasonOptions}
placeholder={select.label}
autoFocus
value={select.value}
onChange={handler}
/>
Which calls this custom hook:
const handlePlayerStats = async (url) => {
try {
const req = await fetch(url).then((response) => response.json());
console.log(req);
if (req.messageNumber) {
setFetchedData([]);
} else if (req.stats[0].splits.length > 0) {
setFetchedData(req);
}
} catch (error) {
console.log(error);
}
};
I'm not really sure how to go about looping through all the seasonOptions dynamically and filtering out each season where req.stats[0].splits.length === 0?
Here is the codesandbox link for anyone curious: https://codesandbox.io/s/friendly-kapitsa-c97rzy?file=/components/PlayerStats.js:357-855
To Answer The first parst of your question you can map over this Array of Objects with this method for example using the .map method
React Code SandBox
MDN-.map() method JS
export const seasonOptions = [
{ value: 8471214, label: "198601987" },
{ value: 8471215, label: "198701988" },
{ value: 8471216, label: "198801989" },
{ value: 8471217, label: "198901990" },
{ value: 8471218, label: "199001991" },
{ value: 8471219, label: "199101992" },
{ value: 8471220, label: "199201993" },
{ value: 8471221, label: "199301994" },
{ value: 8471222, label: "199401995" },
{ value: 8471223, label: "199501996" },
];
//MAKE SURE TO MAP OVER THE <option></option> tag not the <Select/>
//CHECK CODE SANDBOX FOR EXAMPLE IN PURE HTML5 AND REACT
{seasonOptions.map((option,index)=>
<Select
key={index}
id="select"
instanceId={"select"}
options={option?.value}
placeholder={option?.label}
autoFocus
value={select.value}
onChange={handler}
/>
)}
Check Out my Answer Here or for other examples here how to map over an array and access it values this method is just 1 of many .
How to find a value from array object in JavaScript? Stack Over Flow Question
For the Second Part Of the Question you can use the new SandBox
Steps
Change the value property here from a string to a number by removing the quotation marks export const seasonOptions = [{value: 8471214, label: "198601987"},]
Assign a useState hook to handle the active filtered item
const [activeFilter, setActiveFilter] = useState([]);
3.Assign an arrow function to handle Filtering the Seasons
using the setTimeOut() method setTimeout()-MDN-DOCS Where 500 is the time the function is executed for that time and
const handleSeasonsFilter = (item) => {
setActiveFilter(item);
setTimeout(() => {
if (!item) {
setFilterSeasons(item);
} else {
setFilterSeasons(
seasonOptions.filter((season) =>
seasonOptions?.includes(season?.label)
)
);
}
}, 500);
};
Pass that to url that searches the API url = `https://statsapi.web.nhl.com/api/v1/people/${activeFilter}/stats?stats=statsSingleSeason&season=200402005 Like in Line 65 in The Sand Box
Display Them using useEffect() hook also add in the values in the dependency array or leave it empty to avoid infinite loops.
useEffect(() => {
//debug data
console.log(stringDataDisplay);
setActiveFilter(activeFilter);
//DEBUG IF VALUE IF PASSED
//setDatasearched(activeFilter);
}, [
/**EMPTY DEPENDENCY ARRAY TO AVOID INFINITE LOOPS */
stringDataDisplay,
activeFilter
]);
in My Example i Displayed Them using another useState Hook and State action
const [datasearched, setDatasearched] = useState([])
& Finally Just Assigned a new const stringDataDisplay = JSON.stringify([datasearched]);
To Stringify the [datasearched] Array Here.
Note
Make sure to pass the handleSeasonsFilter to OnClick as an empty arrow function and pass the option.value property as a String so the API Accepts the request.
Hope this helps with your Example and Ill try to Check the code sandbox also with your method.
Bear in Mind i still i am developing this and i understand you want the values of the seasons to be shown when no player id is selected Am i correct?
I have a bunch of expansion panels that are rendered from a list. This list has 60+ items in it, so I have set up a pagination to display 5 results at a time. The problem is that the expansion panels do not correctly update with the new lists. I Know that I am returning a new list.
{{this.viewFilteredTools}}
<success-factors v-for="(x,index) in this.viewFilteredTools" :key="index" :factor="x" :tool="tool" />
{{x}}
</v-expansion-panels>
<v-pagination
v-model="page"
:length="this.paginationLength/5"></v-pagination>
This is just holding the expansion panels which are coming in through <success-factors/>.
<script>
import SuccessFactors from './SuccessFactors.vue';
export default {
components: { SuccessFactors },
props:{
tool:{
type: Object,
required: false,
}
},
data() {
return {
page:1,
paginationLength: 0,
factors: {},
factorsList: [],
filterByItems: [
{text:'A to Z (Complete Reviews First)',value:'ascending'},
{text:'Z to A (Complete Reviews First)',value:'descending'},
{text:'A to Z (all)',value:'allAscending'},
{text:'Z to A (all)',value:'allDescending'}
],
filterValue: {text:'Z to A (all)',value:'allDescending'},
viewFilteredTools:[]
};
},
mounted() {
console.log('something happens here')
this.factorsList = this.tool.factors;
this.paginateResults(1,this.factorsList)
this.paginationLength = this.factorsList.length
},
watch: {
page(oldPage){
this.paginateResults(oldPage,this.factorsList);
}
},
// computed:
async fetch() {
const { $content} = this.$nuxt.context;
this.factors = (await $content(this.tool['factors']).fetch());
this.factorsList = this.tool.factors;
},
methods: {
sortList(lstValue) {
console.log(this.tool.factors);
let sortFactors = this.tool.factors;
sortFactors = sortFactors.sort((a,b) => {
if(a<b){
return -1;
}
if(a>b){
return 1;
}
return 0;
})
this.factors = sortFactors;
},
paginateResults(page,results){
console.log(results)
const startCount = (page-1)*5;
const endCount = startCount + 5;
this.startCount = startCount+1;
this.endCount = endCount;
console.log(results.slice(startCount,endCount))
this.viewFilteredTools = results.slice(startCount,endCount);
}
}
};
</script>
this.viewFilteredTools is created from this.factorsList inside the mount lifecycle. Then any time a new page is chosen it is updated from the method sortList(). I can see the viewFilteredTools does change with every page change due to printing it out above the <success-factors>. However, the <success-factors> does not take the data from the updated lists.
Your best bet is to use a computed property. Vue knows when a computed property is updated & would re-render when that property updates.
I am a newbie in vue.js. I have a problem with side effect in computed property. I'm not sure why i'm getting an unexpected side effect in computer property error with the code below. ESlint shows me this error in console. I found what does it mean, but I dont have any idea how to change. Any ideas?
export default {
name: "Repaid",
components: {
VueSlideBar
},
data() {
return {
response: {},
slider: {
lineHeight: 8,
value: 2000,
data: []
},
days: {},
monthCounts: [5],
currentMonthCount: 5,
isLoading: false,
errorMessage: "",
validationError: ""
};
},
computed: {
finalPrice() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index]; =>this is my side effect
const payment = this.response.prices[this.days]
[this.slider.value].schedule[0].amount;
}
},
I read that i should add slide() to my variable that.days, to mutate the original object. Or maybe I should carry them to the methodes.
EDIT
I carried all my calculations to methods and trigger a function in computed property.
Now it looks like this:
methods: {
showPeriod() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index];
const payment = this.response.prices[this.days][this.slider.value]
.schedule[0].amount;
return "R$ " + payment;
},
}
computed: {
finalPrice() {
if (!this.response.prices || this.monthCounts === []) {
return "";
}
return this.showPeriod();
}
},
It's coming from "eslint-plugin-vue" and the link for that rule is below,
https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html
Either you override this rule in your eslint rules file or you can simply turn off the eslint for this specific line like below
this.days = Object.keys(this.response.prices)[index]; // eslint-disable-line
--
One more thing (Not related to your question) is that you need to return some value in computed.
I have a reactJS application where I am trying to dynamically render some data that I read in with a fetch() promise. This is the code of my application:
import React from 'react';
import '../styles/app.css';
//think of react components as functions
class Testpage2 extends React.Component {
constructor(props) {
super(props);
this.state = {
numberOfRecords: 0,
productArray: [{
barcode: '',
name: ''
}]
};
}
componentDidMount() {
let currentComponent = this;
var recordCount = 0;
var tempData = [];
//Make use of the API not the web service.
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
recordCount = -2;
} else {
tempData = JSON.parse(myJson[0].return_string);
recordCount = tempData.barcode.length;
}
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: currentComponent.state.productArray.push(
{
name: tempData.name,
barcode: tempData.barcode
})
}
);
}
});
}
render() {
console.log(this.state.productArray);
return (
<div>
{ this.state.productArray.map((prod, index) => <li key={index}>{prod.barcode}</li>)}
</div>
)
}
}
export default Testpage2
and this is the error message that I am getting:
Uncaught (in promise) TypeError: this.state.productArray.map is not a function
at Testpage2.render (testpage2.js:67)
This is the result of the console.log() that I added in the render() function:
I'm not really sure what this error is telling me or how to go about debugging the issue.
Any help is greatly appreciated.
Thank you.
The return type of array.push is the new length of the array aka a number
So you set the state property productArray to a number and then try to call number.map which is not defined
How to fix?
push first and then use that array to set the state
const updatedArray = [...currentComponent.state.productArray]
updatedArray.push({ name: tempData.name, barcode: tempData.barcode })
currentComponent.setState({
numberOfRecords: recordCount,
productArray: updatedArray
}
Resources:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
According to MDN:
The push() method adds one or more elements to the end of an array and returns the new length of the array.
It appears that your code expects that Array.push() will return the modified array itself:
productArray: currentComponent.state.productArray.push(...
To prevent the state corruption you should do construct the new array separately, before invoking setState().
Array's push() function returns integer, so you cannot call map() function on it. Try to change your function to:
currentComponent.setState({
numberOfRecords: recordCount,
productArray: [...currentComponent.state.productArray, {
name: tempData.name,
barcode: tempData.barcode
}]
})
The JavaScript Array.push method does not return the modified array, it returns the new length of the array, which is a number. Numbers in JavaScript do not have the map method.
You need to do first create a clone of the productArray, then push the new data, and finally set state:
const newProductArray = [...currentComponent.state.productArray]
newProductArray.push({
name: tempData.name,
barcode: tempData.barcode
})
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: newProductArray
}
)
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push