How to properly iterate or loop through an objects in react - reactjs

The code below queries a record from an Atlassian Storage API.
with console.log(data) displays the records as objects objects.
with console.log(data.first_name) and console.log(data.last_name), I can successfully see the name Lucy and Carrots in the console.
Here is my Issue:
When I try to loop through the objects in other to display the records as per code below. it displays Error
TypeError: Cannot read property 'length' of undefined
at Object.App
If I remove the projects.length and try to display records, it will show error
TypeError: Cannot read property 'map' of undefined
Below is my effort so far
import api, { route } from "#forge/api";
import ForgeUI, { render, Fragment, Text, IssuePanel, useProductContext, useState, Component, useEffect} from "#forge/ui";
import { storage} from '#forge/api';
const fetchData = async () => {
//const data = {first_name: 'Lucy', last_name: 'Carrots' };
const data = await storage.get('key1');
console.log(data);
console.log(data.first_name);
console.log(data.last_name);
};
const App = () => {
const [ projects ] = useState(fetchData);
fetchData();
return (
<Fragment>
<Text> Display Objects Records</Text>
{projects.length ? projects.map((i, v) => (
<Text key={v}>
<Text>
<Text>First Name: {v.first_name}</Text>
<Text>Last Name: {v.last_name}</Text>
</Text></Text>
)): <Text>No data stored yet...</Text>}
</Fragment>
);
};
export const run = render(
<IssuePanel>
<App />
</IssuePanel>
);

I see the response data return an object, so you don't need to use array with map.
And you should call API in the useEffect.
const App = () => {
const [projects, setProjects] = useState(null);
useEffect(() => {
const fetchData = async () => {
const data = await storage.get("key1");
setProjects(data);
};
fetchData();
}, []);
...
{
projects ? (
<Text key={v}>
<Text>
<Text>First Name: {projects.first_name}</Text>
<Text>Last Name: {projects.last_name}</Text>
</Text>
</Text>
) : (
<Text>No data stored yet...</Text>
);
}
...
}

Try const [projects, setProjects] = useState([]) instead of const [projects] = useState(fetchData). This will make your state start out as an empty array that the API call can exchange for a filled one later by using setProjects().
Then, inside fetchProjects, you can call setProjects(data) to update your state, which will rerender your page.
Finally, calling fetchData() in the place you do now will cause it to be called every time your App is rendered, which is inefficient.
Instead, call it inside a useEffect() hook:
useEffect(fetchData, []);
This will call fetchData() once when the page is loaded.

Related

React using fetch returns undefined until save

new to react so I am not quite sure what I am doing wrong here... I am trying to call data from an API, then use this data to populate a charts.js based component. When I cmd + s, the API data is called in the console, but if I refresh I get 'Undefined'.
I know I am missing some key understanding about the useEffect hook here, but i just cant figure it out? All I want is to be able to access the array data in my component, so I can push the required values to an array... ive commented out my attempt at the for loop too..
Any advice would be greatly appreciated! My not so functional code below:
import React, {useState, useEffect} from 'react'
import {Pie} from 'react-chartjs-2'
const Piegraph = () => {
const [chartData, setChartData] = useState();
const [apiValue, setApiValue] = useState();
useEffect(async() => {
const response = await fetch('https://api.spacexdata.com/v4/launches/past');
const data = await response.json();
const item = data.results;
setApiValue(item);
chart();
},[]);
const chart = () => {
console.log(apiValue);
const success = [];
const failure = [];
// for(var i = 0; i < apiValue.length; i++){
// if(apiValue[i].success === true){
// success.push("success");
// } else if (apiValue[i].success === false){
// failure.push("failure");
// }
// }
var chartSuccess = success.length;
var chartFail = failure.length;
setChartData({
labels: ['Success', 'Fail'],
datasets: [
{
label: 'Space X Launch Statistics',
data: [chartSuccess, chartFail],
backgroundColor: ['rgba(75,192,192,0.6)'],
borderWidth: 4
}
]
})
}
return (
<div className="chart_item" >
<Pie data={chartData} />
</div>
);
}
export default Piegraph;
There are a couple issues that need sorting out here. First, you can't pass an async function directly to the useEffect hook. Instead, define the async function inside the hook's callback and call it immediately.
Next, chartData is entirely derived from the apiCall, so you can make that derived rather than being its own state variable.
import React, { useState, useEffect } from "react";
import { Pie } from "react-chartjs-2";
const Piegraph = () => {
const [apiValue, setApiValue] = useState([]);
useEffect(() => {
async function loadData() {
const response = await fetch(
"https://api.spacexdata.com/v4/launches/past"
);
const data = await response.json();
const item = data.results;
setApiValue(item);
}
loadData();
}, []);
const success = apiValue.filter((v) => v.success);
const failure = apiValue.filter((v) => !v.success);
const chartSuccess = success.length;
const chartFail = failure.length;
const chartData = {
labels: ["Success", "Fail"],
datasets: [
{
label: "Space X Launch Statistics",
data: [chartSuccess, chartFail],
backgroundColor: ["rgba(75,192,192,0.6)"],
borderWidth: 4,
},
],
};
return (
<div className="chart_item">
<Pie data={chartData} />
</div>
);
};
export default Piegraph;
pull your chart algorithm outside or send item in. Like this
useEffect(async() => {
...
// everything is good here
chart(item)
})
you might wonder why I need to send it in. Because inside useEffect, your apiValue isn't updated to the new value yet.
And if you put the console.log outside of chart().
console.log(apiData)
const chart = () => {
}
you'll get the value to be latest :) amazing ?
A quick explanation is that, the Piegraph is called whenever a state is updated. But this update happens a bit late in the next cycle. So the value won't be latest within useEffect.

Objects are not valid as a React child - found: object with keys

I try to setState but it does not accept the JSON responses I fetch from a URL. but the response looks ok, it is an array of objects:
{"songs":
[
{"id":1,"name":"Hello","singer":"Adele","img":"adele.png","type":"pop","mp3":"Adele.mp3"},
{"id":2,"name":"de una vez","singer":"Selena gomez","img":"selena.png","type":"pop","mp3":"Selena.mp3"},
{"id":3,"name":"Bayda","singer":"Navid","img":"navid.png","type":"pop","mp3":"Navid.mp3"},
{"id":4,"name":"Takin' Back My Love ","singer":"Enrique Iglesias","img":"enrique.png","type":"Pop","mp3":"Enrique.mp3"}
]
}
and here is my react component:
import React, {useState, useEffect} from "react";
const App = () => {
const [songs, setSongs] = useState([]);
const [playing, setPlaying] = useState({});
useEffect(() => {
fetch(`http://localhost:8765`)
.then(res => res.json())
.then(jsonRes => {
// setPlaying(jsonRes[0]);
setSongs(jsonRes.songs);
// jsonRes.songs.map(song => setSongs([...song]))
console.log('Songs are: ', jsonRes.songs);
});
}, []);
// console.log('songs:', songs)
// console.log('playing:', playing)
return (
<div>
Hello from App Songs: {songs}
</div>
);
}
export default App;
You should use map to display an array in React.
Read more: https://reactjs.org/docs/lists-and-keys.html
This songs array is an array of objects and you cannont pass it to the return statement. If you want to have the content of if you need to do as below in your return statement, for example you want to show the name of each song:
{songs.map(song => {
return (
<div>{song.name}</div>
)
})}

React js giving error Objects are not valid as a React child, I used hooks

I am sending data from Node JS to React JS in array object. In React JS when I am setting response data I am getting error "Objects are not valid as a React child (found: object with keys {eventName, eventDate, merchantid}). If you meant to render a collection of children, use an array instead."
I checked one of the Stackoverflow post useState Array of Objects. I am also setting value same way, but I am getting error.
Below data I am sending from Node JS.
[
{
eventName: 'Sankranti',
eventDate: 2021-01-21T00:00:00.000Z,
merchantid: 'tp012345'
},
{
eventName: 'Sankranti 1',
eventDate: 2021-01-26T00:00:00.000Z,
merchantid: 'tp012345'
}
]
Below screen shot I can see error and response data on the console.
Below my code, I am getting error at setEventList(eventList => [...eventList, response]). Based on comments I added below code.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import Carousel from 'react-bootstrap/Carousel'
import axios from 'axios';
import DashboardNavBar from './DashboardNavBar';
import Header from './Header';
const DashboardPage = (props) => {
const [eventList, setEventList] = useState([])
const [index, setIndex] = useState()
if (!props.profileData) {
useEffect(() => {
(async () => {
const eventsList = await axios.get(
"http://localhost:3000/api/dashboard"
);
console.log(eventsList.data)
const response = eventsList.data
setEventList(eventList => [...eventList, response])
if(!response){
setErrorMsg('Please create Event and then add User !!')
}
})();
}, []);
}
const eventListRender = eventList.length > 0 ?
eventList.map((item,index) => {
console.log('item name: ', item[index].eventName)
return <Carousel.Item>{item[index].eventName}</Carousel.Item>
}) :
<Carousel.Item>No upcoming events</Carousel.Item>
const handleSelect = (selectedIndex) => {
console.log(eventList)
console.log(selectedIndex)
setIndex(selectedIndex)
}
return (
<div>
<DashboardNavBar />
<Header />
<p >Welcome !!!!!!</p>
<Carousel
activeIndex={index}
onSelect={handleSelect}
>
{eventListRender}
</Carousel>
</div>
);
}
const mapStateToProps = (state) => ({
profileData: state.auth.profileData
})
export default connect(mapStateToProps) (DashboardPage);
After adding below code it always reading first occurrence
const eventListRender = eventList.length > 0 ?
eventList.map((item,index) => {
console.log('item name: ', item[index].eventName)
return <Carousel.Item>{item[index].eventName}</Carousel.Item>
}) :
<Carousel.Item>No upcoming events</Carousel.Item>
Please find the updated results
Issue
Ok, your codesandbox confirms what I suspected. In your sandbox you've directly placed that mock response in your state as a flat array
const [eventList, setEventList] = useState([
{
eventName: "Sankranti",
eventDate: "2021-01-21T00:00:00.000Z",
merchantid: "tp012345"
},
{
eventName: "Sankranti 1",
eventDate: "2021-01-26T00:00:00.000Z",
merchantid: "tp012345"
}
]);
This allows the render to work as you expected, simply mapping over this flat array of objects.
eventList.map((item, index) => {
return <Carousel.Item>{item.eventName}</Carousel.Item>;
})
But in your original code you are not updating your state to be a flat array. The response is an array, i.e. [object1, object2] and you append this array to the end of your state's eventList array.
setEventList(eventList => [...eventList, response])
This updates your state to something like this [[object1, object2]], so the mapping function you used only maps one element.
eventList.map((item, index) => {
return <Carousel.Item>{item[index].eventName}</Carousel.Item>;
})
The reason is because you used the array index of the outer (recall eventList is an array of length 1) to access into the inner nested array (array of length 2). In iterating the outer array the index only reaches value 0, so only the zeroth element of the inner array is rendered.
See a more accurate reproduction of your issue in this code:
const response = [
{
eventName: "Sankranti",
eventDate: "2021-01-21T00:00:00.000Z",
merchantid: "tp012345"
},
{
eventName: "Sankranti 1",
eventDate: "2021-01-26T00:00:00.000Z",
merchantid: "tp012345"
}
];
function App() {
const [eventList, setEventList] = useState([]);
useEffect(() => {
setEventList((eventList) => [...eventList, response]);
}, []);
const eventListRender =
eventList.length > 0 ? (
eventList.map((item, index) => {
return <Carousel.Item>{item[index].eventName}</Carousel.Item>;
})
) : (
<Carousel.Item>No upcoming events</Carousel.Item>
);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Carousel>{eventListRender}</Carousel>
</div>
);
}
Solution
If the response data is also an array then it seems you should spread it into your eventList state array so it remains a nice, flat array.
Additionally, as pointed out by #Ashish, your useEffect hook usage is invalid and breaks the rules of hooks by being placed in a conditional block. The effect needs to be in the body of the component, so the condition should be tested in the effect callback. Refactor the anonymous async function to be a standard named function, and invoke in a conditional check within the effect callback.
useEffect(() => {
const getEvents = async () => {
const eventsList = await axios.get("http://localhost:3000/api/dashboard");
console.log(eventsList.data);
const response = eventsList.data;
setEventList((eventList) => [
...eventList, // <-- copy previous state
...response, // <-- spread array response
]);
if (!response) {
setErrorMsg("Please create Event and then add User !!");
}
};
if (!props.profileData) { // <-- check condition for fetching data
getEvents();
}
}, []);
const eventListRender =
eventList.length > 0 ? (
eventList.map((item, index) => {
return <Carousel.Item key={index}>{item.eventName}</Carousel.Item>;
})
) : (
<Carousel.Item>No upcoming events</Carousel.Item>
);
Demo with mocked axios data fetch.

Loading spinner not showing in React Component

I am creating a React.js app which got 2 components - The main one is a container for the 2nd and is responsible for retrieving the information from a web api and then pass it to the child component which is responsible for displaying the info in a list of items. The displaying component is supposed to present a loading spinner while waiting for the data items from the parent component.
The problem is that when the app is loaded, I first get an empty list of items and then all of a sudden all the info is loaded to the list, without the spinner ever showing. I get a filter first in one of the useEffects and based on that info, I am bringing the items themselves.
The parent is doing something like this:
useEffect(() =>
{
async function getNames()
{
setIsLoading(true);
const names = await WebAPI.getNames();
setAllNames(names);
setSelectedName(names[0]);
setIsLoading(false);
};
getNames();
} ,[]);
useEffect(() =>
{
async function getItems()
{
setIsLoading(true);
const items= await WebAPI.getItems(selectedName);
setAllItems(items);
setIsLoading(false);
};
getTenants();
},[selectedName]);
.
.
.
return (
<DisplayItems items={allItems} isLoading={isLoading} />
);
And the child components is doing something like this:
let spinner = props.isLoading ? <Spinner className="SpinnerStyle" /> : null; //please assume no issues in Spinner component
let items = props.items;
return (
{spinner}
{items}
)
I'm guessing that the problem is that the setEffect is asynchronous which is why the component is first loaded with isLoading false and then the entire action of setEffect is invoked before actually changing the state? Since I do assume here that I first set the isLoading and then there's a rerender and then we continue to the rest of the code on useEffect. I'm not sure how to do it correctly
The problem was with the asynchronicity when using mulitple useEffect. What I did to solve the issue was adding another spinner for the filters values I mentioned, and then the useEffect responsible for retrieving the values set is loading for that specific spinner, while the other for retrieving the items themselves set the isLoading for the main spinner of the items.
instead of doing it like you are I would slightly tweak it:
remove setIsLoading(true); from below
useEffect(() =>
{
async function getNames()
{
setIsLoading(true); //REMOVE THIS LINE
const names = await WebAPI.getNames();
setAllNames(names);
setSelectedName(names[0]);
setIsLoading(false);
};
getNames();
} ,[]);
and have isLoading set to true in your initial state. that way, it's always going to show loading until you explicitly tell it not to. i.e. when you have got your data
also change the rendering to this:
let items = props.items;
return isLoading ? (
<Spinner className="SpinnerStyle" />
) : <div> {items} </div>
this is full example with loading :
const fakeApi = (name) =>
new Promise((resolve)=> {
setTimeout(() => {
resolve([{ name: "Mike", id: 1 }, { name: "James", id: 2 }].filter(item=>item.name===name));
}, 3000);
})
const getName =()=> new Promise((resolve)=> {
setTimeout(() => {
resolve("Mike");
}, 3000);
})
const Parent = () => {
const [name, setName] = React.useState();
const [data, setData] = React.useState();
const [loading, setLoading] = React.useState(false);
const fetchData =(name) =>{
if(!loading) setLoading(true);
fakeApi(name).then(res=>
setData(res)
)
}
const fetchName = ()=>{
setLoading(true);
getName().then(res=> setName(res))
}
React.useEffect(() => {
fetchName();
}, []);
React.useEffect(() => {
if(name)fetchData(name);
}, [name]);
React.useEffect(() => {
if(data && loading) setLoading(false)
}, [data]);
return (
<div>
{loading
? "Loading..."
: data && data.map((d)=>(<Child key={d.id} {...d} />))}
</div>
);
};
const Child = ({ name,id }) =>(<div>{name} {id}</div>)
ReactDOM.render(<Parent/>,document.getElementById("root"))
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React navigation clearing out data when stored in AsyncStorage - How to view data?

I'm using react navigation bottom tab and when I have data stored in my AsyncStorage, the data comes out to be empty when I go into this SavedData component. In the console, if I showAsyncStorageContentInDev(), it shows that I do indeed have the key #storage_key. But when I console log out the data, its always empty. Does it have to do with something with react navigation bottom tab that empty's out the data?
Or how do I view the data when user tabs into this component?
const SavedData = (props) => {
const [getData, setData] = useState([]);
const getKeyInfo = async () => {
try {
const storedData = await AsyncStorage.getItem('#storage_Key');
setData(JSON.parse(storedData))
console.log('saved',getData);
} catch (e) {
// saving error
}
console.log(getData);
};
useEffect(() => {
console.log(getData);
getKeyInfo();
}, []);
return (
<View>
<Text>
{getData.map((i, index) => (
<View key={i.id.toString()}>
<Data uri={i.uri} />
</View>
))}
;
</Text>
</View>
);
};
export default SavedExercise;
Have you ever try pass getData into the second argument of useEffect to notify for Component re-render when state change:
useEffect(() => {
console.log(getData);
getKeyInfo();
}, [getData]); // Here

Resources