Data is dissapearing eventhough it is there at the same time - reactjs

anybody know why the data exists in line 267 but not in 268? Any help is appreciated , thank you.
code
console
full function with useEffect watching props value to trigger the function
function createData() {
//loop through employees and create obj array
const emptyArray = [];
let counter = 0;
if (props.employees) {
const rowData = props.employees.map((item) => {
console.log(item.tests_taken);
let objectDetails = {
firstName: item.first_name,
lastName: item.last_name
};
console.log(item);
console.log(item.tests_taken[0]);
if (item.tests_taken[0]) {
console.log(item.tests_taken[0]);
item.tests_taken[0].forEach((test) => {
console.log(test[Object.keys(test)[0]]);
console.log('yo');
objectDetails = {
...objectDetails,
id: counter,
cefrLevel: test[Object.keys(test)[0]].overallScore.level,
cefrDescription:
test[Object.keys(test)[0]].overallScore.description,
overallScore: test[Object.keys(test)[0]].overallScore.score + '%',
assessmentDate: test[Object.keys(test)[0]].date
};
counter += 1;
emptyArray.push(objectDetails);
});
//console.log(emptyArray);
return objectDetails;
}
props.setEmployees(null);
});
setUsersArray(emptyArray);
console.log(usersArray);
return rowData;
}
}
useEffect(() => {
createData();
}, [props.employees]);

Related

Get TypeError: Cannot read properties of undefined (reading 'forEach') when pass a params

At first, the params does not have any data yet (blank array), but it will update again after useEffect set the variable.
But for my highchart, it gave me this error.
TypeError: Cannot read properties of undefined (reading 'forEach')
52 | createChart();
53 | } else {
54 | if (props.allowChartUpdate !== false) {
>55 | if (!props.immutable && chartRef.current) {
| ^ 56 | chartRef.current.update(
57 | props.options,
58 | ...(props.updateArgs || [true, true])
I searched some solutions, they suggest can use allowChartUpdate={false} and immutable={false} for solving the problem. After I tried it, yes it does solve my problem, but my highchart doesn't show the data when initial load.
I'm guessing is it the params passing in a blank array at first, then passing a second time with actual values so it causes this problem. If yes, can rerendering the highchart solve the problem? And how can I do that?
Here is the link, please help me on it. Thank you muakzzz.
You can instead just provide the getRouteData function as an initializer function directly to the useState hook to provide the initial state. So long as it's not asynchronous it will provide initial state for the initial render, no need to use the useEffect hook to populate state after the first render.
Additionally, you should initialize routeMapData to have the data property array by default so you don't accidentally pass it through with an undefined data property, which was part of the problem you were seeing.
export default function App() {
const [routeData] = useState(getRouteData()); // <-- initialize state
const mapStation = () => {
const routeMapData = {
data: [], // <-- data array to push into
};
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
if (nextStation) {
routeMapData.data.push([ // <-- push into array
firstStation.stationName,
nextStation.stationName
]);
}
}
}
}
return routeMapData;
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
You don't need to even use the local state as you can directly consume the returned array from getRouteData in the mapStation utility function.
export default function App() {
const mapStation = () => {
return {
data: getRouteData().flatMap(({ station }) => {
return station.reduce((segments, current, i, stations) => {
if (stations[i + 1]) {
segments.push([
current.stationName,
stations[i + 1].stationName
]);
}
return segments;
}, []);
})
};
};
const content = (key) => {
if (key === "map") {
return <RouteMap mapRouteData={mapStation()} />;
}
return null;
};
return <Box className="rightPaper center">{content("map")}</Box>;
}
Thank you for your help. I managed to get my desired output already. The problem was my parent component will pass a blank array of data into my highchart network graph component at first due to the useEffect used in the parent. And after that, they pass another array with actual data into my highchart network graph component.
import React, {useEffect, useState} from 'react';
import {Box} from "#mui/material";
import RouteMap from "./Content/RouteMap";
import {getRouteData} from "../../../API/RouteDataAPI"
import Timetable from "./Content/Timetable";
import _ from 'lodash';
function RightContent({contentKey}) {
const [routeData, setRouteData] = useState([]);
useEffect(() => {
getRouteData().then(res => setRouteData(res));
}, [])
const mapStation = () => {
let arr = [], allStation = [], routeMapData = {}
if (routeData.length !== 0) {
for (let i = 0; i < routeData.length; i++) {
const station = routeData[i].station;
for (let j = 0; j < station.length; j++) {
const firstStation = station[j];
const nextStation = station[j + 1];
allStation.push(firstStation.stationName)
if (nextStation) {
arr.push([firstStation.stationName, nextStation.stationName])
}
}
}
routeMapData.data = arr;
routeMapData.allStation = allStation;
routeMapData.centralStation = "KL Sentral"
}
return routeMapData;
}
// const mapStation = () => {
// let arr = [];
// getRouteData().then(res => {
// arr.push(res.flatMap(({station}) => {
// return station.reduce((segments, current, i, stations) => {
// if (stations[i + 1]) {
// segments.push(
// current.stationName,
// );
// }
// return segments;
// }, []);
// }))
// })
// console.log(arr)
// }
const content = (key) => {
const availableRoute = routeData.map(route => route.routeTitle);
if (
key === 'map'
// && !_.isEmpty(mapStation())
){
// console.log('here', mapRouteData)
return <RouteMap mapRouteData={mapStation()}/>;
}
else if (availableRoute.includes(key)) {
return <Timetable routeData={routeData} currentRoute={key}/>
} else {
return null;
}
}
return (
<Box className="rightPaper center">
{content(contentKey)}
</Box>
);
}
export default RightContent;
^^^This was my parent component.^^^
In the content variable function there, I have an if statement with the requirements provided. If you try to uncomment the lodash (second requirement) in the if statement, I can able to get my desire result.
This was my highchart network component.
import React, {useEffect, useRef, useState} from 'react'
import Highcharts from 'highcharts/highstock'
import HighchartsReact from 'highcharts-react-official'
import networkgraph from 'highcharts/modules/networkgraph'
require('highcharts/modules/exporting')(Highcharts);
require('highcharts/modules/export-data')(Highcharts);
if (typeof Highcharts === "object") {
networkgraph(Highcharts);
}
const RouteMap = ({mapRouteData}) => {
const [seriesData, setSeriesData] = useState(mapRouteData.data);
const [centralStation, setCentralStation] = useState(mapRouteData.centralStation);
const [allStation, setAllStation] = useState(mapRouteData.allStation);
useEffect(() => {
setSeriesData(mapRouteData.data);
setCentralStation(mapRouteData.centralStation);
setAllStation(mapRouteData.allStation);
}, [mapRouteData])
Highcharts.addEvent(
Highcharts.Series,
'afterSetOptions',
function (e) {
let colors = Highcharts.getOptions().colors,
i = 0,
nodes = {};
if (
this instanceof Highcharts.seriesTypes.networkgraph &&
e.options.id === 'lang-tree' &&
e.options.data !== undefined
) {
let lastSecond = '', arry = []
e.options.data.forEach(function (link) {
if (lastSecond !== link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[++i]
}
} else if (lastSecond === link[0]) {
nodes[link[0]] = {
id: link[0],
color: colors[i]
}
nodes[link[1]] = {
id: link[1],
color: colors[i]
}
arry.push(link[0])
}
lastSecond = link[1];
});
const exchangeStation = allStation.filter((item, index) => allStation.indexOf(item) !== index);
i += 1;
exchangeStation.forEach((station) => {
nodes[station] = {
id: station,
marker: {
radius: 18
},
name: 'Interchange: ' + station,
color: colors[i]
}
})
nodes[centralStation] = {
id: centralStation,
name: 'Sentral Station: ' + centralStation,
marker: {
radius: 25
},
color: colors[++i]
}
e.options.nodes = Object.keys(nodes).map(function (id) {
return nodes[id];
});
}
}
);
const options = {
chart: {
type: 'networkgraph',
},
title: {
text: 'The Route Map'
},
caption: {
text: "Click the button at top right for more options."
},
credits: {
enabled: false
},
plotOptions: {
networkgraph: {
keys: ['from', 'to'],
layoutAlgorithm: {
enableSimulation: true,
// linkLength: 7
}
}
},
series: [
{
link: {
width: 4,
},
marker: {
radius: 10
},
dataLabels: {
enabled: true,
linkFormat: "",
allowOverlap: false
},
id: "lang-tree",
data: seriesData
}
]
};
return <HighchartsReact
ref={useRef()}
containerProps={{style: {height: "100%", width: "100%"}}}
highcharts={Highcharts}
options={options}
/>;
}
export default RouteMap;
Sorry for the lengthy code here. By the way, feel free to let me know any improvements I can make in my code. First touch on react js project and still have a long journey to go.
Once again~ Thank you!
I fixed adding True for allowChartUpdate and immutable
<HighchartsReact
ref={chartRef}
highcharts={Highcharts}
options={options}
containerProps={containerProps}
allowChartUpdate={true}
immutable={true}
/>

How to wait for .map() to finish and generate new keys in the array[index]

I'm trying to generate an array with values as follows:
{ name: 'John', age: 35, employer: 'ABC', paycheck: 5,000, last_paycheck: 4,900, change: 100 } // new array
with the initial values in the array as follow:
{ name: 'John', age: 35, employer: 'ABC' } //inital array
the function convertData() is handling all the array conversion.
async function convertData(data){
if(data.length === 0) return data;
// generates new array
const convertedDataArray = await data.map( async (row) =>{
let name = row.name
let paycheck = 0;
let last_paycheck = 0;
let change = 0;
const response = await axios.get('/getData', {params: {
name,
}});
let apiData = response.data.data;
if(apiData.length > 0){
let newData = apiData[0];
let oldData = apiData[1];
change = newData.payCheck - oldData.payCheck;
paycheck = newData.payCheck;
last_paycheck = oldData.payCheck;
}
console.log(apiData); // prints records up to 100 elements
return {...row, paycheck, last_paycheck, change };
});
console.log(convertedDataArray);// prints [Promise]
return Promise.all(convertedDataArray).then(() =>{
console.log(convertedDataArray); // prints [Promise]
return convertedDataArray;
});
};
where convertData() is called:
const response = await axios.get('/getEmployees',{params: {
token: id,
}});
const dataRows = response.data; //inital array
const tableRows = await convertData(dataRows);
return Promise.all(tableRows).then(() =>{
console.log(tableRows); // prints [Promise]
dispatch(setTableRows(tableRows));
});
I'm not sure why i keep getting Promise return I am still learning how to use promise correctly. Any help would be great, thank you in advance!
You should get a array of promises and use Promises.all to get all the data first.
Then use map() function to construct your data structure.
Example below:
async function convertData(data) {
try {
if (data.length === 0) return data;
const arrayOfPromises = data.map(row =>
axios.get("/getData", {
params: {
name: row.name,
},
})
);
const arrayOfData = await Promise.all(arrayOfPromises);
const convertedDataArray = arrayOfData.map((response, i) => {
const apiData = response.data.data;
let paycheck = 0;
let last_paycheck = 0;
let change = 0;
if (apiData.length > 0) {
const newData = apiData[0];
const oldData = apiData[1];
change = newData.payCheck - oldData.payCheck;
paycheck = newData.payCheck;
last_paycheck = oldData.payCheck;
}
return { ...data[i], paycheck, last_paycheck, change };
});
return convertedDataArray;
} catch (err) {
throw new Error(err);
}
}
(async function run() {
try {
const response = await axios.get("/getEmployees", {
params: {
token: id,
},
});
const dataRows = response.data;
const tableRows = await convertData(dataRows);
dispatch(setTableRows(tableRows));
} catch (err) {
console.log(err);
}
})();

change state variable (array of objects) in react

I am very new to react and have a very straightforward usecase.
on a certain function call, I need to update one of the state variables - which is an array of objects.
I need to iterate through this array find an element and add a key the object in that element.
I tried this way but its not working.
const [finalStudents, setFinalStudents] = useState([]);
function setAttentionForStudent(deviceName, value) {
// console.log("Device ID:", deviceName)
// console.log("Attention value:", value)
finalStudents.map((student, index) => {
console.log("student", student)
if (student['device']['deviceName'] == deviceName) {
console.log("student inside", student)
setFinalStudents((prevFinalStudents) => {
console.log("prev final student",prevFinalStudents)
prevFinalStudents[index]['device']['attentionValue'] = value
})
// student['device']['attentionValue'] = value
} else {
setFinalStudents((prevFinalStudents) => {
prevFinalStudents[index]['device']['attentionValue'] = 0
})
}
})
// console.log(finalStudents)
}
Try this:
const [finalStudents, setFinalStudents] = [];
const setAttentionForStudent = (deviceName, value) => {
if (finalStudents.length !== 0) {
for (var x = 0; x < finalStudents.length; x++) {
if (finalStudents[x].device.deviceName === deviceName) {
finalStudents[x].device.deviceName = value;
setFinalStudents(new Array(...finalStudents));
} else {
finalStudents[x].device.deviceName = value;
setFinalStudents(new Array(...finalStudents));
}
}
}
};
callback in setFinalStudents should return an array to update state. You can use map in setFinalStudents like this:
setFinalStudents((prevFinalStudents) => {
return prevFinalStudents.map((student) => {
if (student["device"]["deviceName"] == deviceName) {
student["device"]["attentionValue"] = value;
} else {
student["device"]["attentionValue"] = value;
}
return student;
});
});
Was finally able to solve the problem by the following way:
setFinalStudents((prevFinalStudents) => {
const clonedFinalStudents = [...prevFinalStudents];
return clonedFinalStudents.map((student) => {
let updatedStudent = { ...student };
let attentionValue = 0;
if (student['device']['deviceName'] == deviceName) {
attentionValue = value;
}
updatedStudent = {
...updatedStudent,
device: {
...updatedStudent.device,
attentionValue,
},
};
return updatedStudent;
});
});

How to add some option to a select box above all mapping in React?

I want to add an All Option to my existing select box.
Select box is creating with some API data. With the API data set I want to add an ALL option above.
This is my code.
const useChemicals = () => {
const [data, setData]: any = useState([]);
useEffect(() => {
const getChemicalsData = async () => {
try {
const results = await searchApi.requestChemicalsList();
if (results.data) {
let groupCount = 0;
const chemList: any = [];
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
chemList.push({
label: chemical.value,
options: [],
});
}
});
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
if (index > 1) {
groupCount += 1;
}
} else {
chemList[groupCount].options.push({
label: chemical.value,
value: chemical.key,
});
}
});
setData([...chemList]);
}
} catch (e) {}
};
getChemicalsData();
}, []);
return data && data;
};
export default useChemicals;
How can I add this. Please help me, I am new to React.

How wait a "Array for each" function?

I got a little problem with synchronous/asynchronous system in the function "Array.foreach".
I don't know how to force my code to wait its end.
I tried to use await/async system but my code did not wait the code in "async responseDB =>".
This is my class:
...
let responsesDTO = [];
await Array.prototype.forEach.call(await searchResponsesByQuestionAndUserId(questions[cpt].idquestion, idUser), async responseDB => {
if(responseDB !== undefined){
const responseDTO = {
response_id:0,
response_text:"",
response_type:""
}
const responseEntity = await searchResponseByResponseId(responseDB.response_id);
responseDTO.response_id = responseDB.response_id;
responseDTO.response_text= responseEntity.text;
responseDTO.response_type= responseDB.type;
responsesDTO.push(responseDTO);
}
});
questionResponse.responses=responsesDTO;
questionResponses[cpt]=questionResponse;
}
Could you help me please? Thanks in advance.
I had to mock your async functions. However, the relevant part is to use for..of instead of forEach
async function searchResponsesByQuestionAndUserId() {
let responsesDB = [];
for (let i = 0; i < 10; i++) {
responsesDB.push({
response_id: parseInt(1000 * Math.random(), 10),
type: 'whatever ' + i
});
}
return new Promise((res) => {
window.setTimeout(() => {
res(responsesDB);
}, 1500);
});
}
async function searchResponseByResponseId(response_id) {
return new Promise((res) => {
window.setTimeout(() => {
res({
text: 'text for response ' + response_id
});
}, 300);
});
}
async function getResponsesDTO() {
let responsesDTO = [],
responsesDB = await searchResponsesByQuestionAndUserId();
for (let responseDB of responsesDB) {
if (responseDB === undefined) {
continue;
}
let responseDTO = {
response_id: 0,
response_text: "",
response_type: ""
},
responseEntity = await searchResponseByResponseId(responseDB.response_id);
responseDTO.response_id = responseDB.response_id;
responseDTO.response_text = responseEntity.text;
responseDTO.response_type = responseDB.type;
responsesDTO.push(responseDTO);
console.log({responseDTO});
}
return responsesDTO;
}
getResponsesDTO().then(responsesDTO => {
console.log(responsesDTO);
});

Resources