Fetching and rendering a component in a loop - reactjs

I have a string array that contains usernames. I want to fetch those users in a loop, put them in an array and render a component for each user. I can retrieve the data from API and print it on console but the code below gives me a white screen.
Here I'm trying to fetch data one by one using fav_list array than contains usernames. Then I want to send the data to another component called InfluencerFavoritesCard and render them. Where I'm doing wrong?
import React from 'react'
import InfluencerFavoritesCard from '../components/influencerFavoritesCard/InfluencerFavoritesCard';
import "./indexPages.css"
import "./favorites.css"
const fav_list = ["cagritaner", "acunilicali", "neslihanatagul"];
async function ListFavorites() {
let array = new Array;
var fetches = [];
for (let i = 0; i < fav_list.length; i++) {
console.log(fav_list[i]);
let uname = fav_list[i];
fetches.push(
fetch(`http://localhost:3001/api/users/${uname}`)
.then(res => {return res.text(); })
.then(res => {
array.push(res);
console.log(res);
}
)
);
}
Promise.all(fetches).then(function () {
console.log(fetches);
console.log(array[0]);
console.log(array.length);
});
return (
<div>
{array.map(item => (< InfluencerFavoritesCard infCard = { item } /> ))}
</div>
);
}
function Favorites() {
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{<ListFavorites/>}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
}
export default Favorites
InfluencerFavoritesCard.jsx
import React from 'react';
import './influencerFavoritesCard.css';
const InfluencerFavoritesCard = ({ infCard }) => {
return (
<div className='infCard'>
<div className='infCard-text-info' >
<div className='infCard-name'>
<h3>{infCard.name}</h3>
</div>
<div className='infCard-username'>
<h4>{infCard.username}</h4>
</div>
<div className='infCard-categories'>
<h4>{infCard.categories}</h4>
</div>
</div>
</div>
)
}
export default InfluencerFavoritesCard;
================================
UPDATED:
I have updated the parent component like below.
export function Favorites() {
const [users, setUsers] = useState([]);
const fav_list = ["cagritaner", "acunilicali", "neslihanatagul"];
useEffect(() => {
const tempUsersCollection = [];
fav_list.map((x, i) => {
fetch(`http://localhost:3001/api/users/${x}`)
.then(res => {return res.json(); })
.then(res => {
tempUsersCollection.push(res.data.tour);
console.log(res);
}
);
});
console.log(tempUsersCollection);
setUsers(tempUsersCollection);
}, []);
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{users.map((item, index) => (
<InfluencerFavoritesCard
infCard={item}
key={`influencer-${item.username}-${index}`}
/>
))}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
}
output of the console.log(tempUsersCollection) (AFTER res.json() !! )
Array []
​
0: Object { username: "neslihanatagul", biography: "contact#neslihanatagul.com", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/5787050…0_AfDfN8wtsD18vya_aLLw6M4UpP8Xx16jb9b4Hsh6cJ3wjA&oe=63A713AF", … }
​
1: Object { username: "cagritaner", biography: "Hüzünlü Bir Ponçik ve Erkeklerin İç Sesi kitaplarının yazarı.\niş birlikleri için; #goygoynetworkinfo", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/4424442…0_AfCXmyBbUrKQjpzsGEHNEMHXoNP7HZ8UKaYXHAL4S0DFlA&oe=63A6926B", … }
​
2: Object { username: "acunilicali", biography: "Acun Ilıcalı Resmi Instagram Hesabıdır.", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/1193932…0_AfDuL0toVKsSGOrnXWISBMAR78G79QwNfxJmm5cNFBuW2A&oe=63A78969", … }
​
length: 3
​
<prototype>: Array []
Favorites.jsx:136

The thing is that return statement is being rendered before all the promises are resolved, meaning that it's empty. This is exactly how it should work, so no bug here.
What you need to do is as other mentioned, use useState and useEffect to control the data:
// This will hold your collection
const [users, setUsers] = useState([])
...
// And here you need to update that collection
useEffect(()=>{
const temp = []
fetch(`http://localhost:3001/api/users/${uname}`)
.then(res => {return res.text(); })
.then(res => {
temp.push(res);
}
)
setUsers(temp)
}, [])
Later on the return you can do this:
// This controls if there are no users
if(users.length <= 0){
return <>There are no users</>
}
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{users.map(item => (<InfluencerFavoritesCard infCard = { item } /> ))}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
Created a CodeSandbox so you can see this working
UPDATE:
// Call the function after the first render
useEffect(() => {
fetchUsers();
}, []);
// Wrapped all the calls in a Promise.all and update the state
async function fetchUsers() {
const response = await Promise.all(
fav_list.map((x) =>
fetch(`https://pokeapi.co/api/v2/pokemon/${x}`)
.then((res) => res.json())
.then((user) => user)
)
);
console.log(response);
setUsers(response);
}
// Handle the case where nothing is retrieved
if (users.length <= 0) {
return <>There are no users</>;
}

To fetch data asynchronously, you need to use the useEffect hook. You should store the data using a useState hook and then set that data when you get a response from your fetch request.

Related

Error: Objects are not valid as a React child (found: object with keys {links}). If you meant to render a collection of children, use an array instead

Im trying to figure out how to handle different API responses and map over them. So i can present them in my application.
Handeling the getArrayofObjects function is no problem at all. However, the Nasa API contains object structure with nested objects within.
{
element_count: 25,
links: {
next: https:link1.com,
prev: https:linkprev.com,
self: https:linkself.com }
},
near_earth_objects: {
2015-09-08: [{absolute_magnitude_h: 19.3,
close_approach_data: [{close_approachdate: 2015:01:12}]},
estimated-diameter: {}, id: 132342323, links: {}]
i want to map over this object however react gives me the error mentioned in the title. How can i map over this data and allow it to be presented orderly in the application?
I have the following component:
import styles from '../styles/Home.module.css'
const axios = require('axios')
import { useState } from 'react'
export default function Home() {
const [nasa, setNasa] = useState([])
const [posts, setPosts] = useState([])
const returnNestedObjects = () => {
axios
.get(
'https://api.nasa.gov/neo/rest/v1/feed?start_date=2015-09-07&end_date=2015-09-08&api_key=hqIAqlEjXdGOE4K0H44Oj0Bq20tID1ytS3IdYuT4'
) // goed kijken naar de API of het de juiste data heeft als format
.then((response) => {
setNasa(response.data)
console.log('Objects of nested NASA objects', response.data) // returs object with nested objects // TODO uitvogelen hoe je deze mapped
})
.catch((error) => {
setIsError(true)
console.log(error)
})
}
const results = Object.keys(nasa).map((key) => {
console.log(key) // 👉️ name, element_count, near_earth_objects, links console logt the key
console.log(nasa[key]) // 👉️ {next: somelink.com/} 25, {2015-09-08} console logt the value
return { [key]: nasa[key] } // returns key + value
})
console.log('results', results)
const stringifyObjects = JSON.stringify(results)
const getArrayofObjects = () => {
axios
.get('https://jsonplaceholder.typicode.com/posts') // returns Array
.then((response) => {
setPosts(response.data)
console.log('Array Of objects', response.data) // returs Array with nested objects => .map to map over them
})
.catch((error) => {
setIsError(true)
console.log(error)
})
}
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to{' '}
<span className={styles.headerTitle}>
<h2>Testing API API</h2>
</span>
</h1>
<div className={styles.buttoncontainer}>
<button onClick={returnNestedObjects}>Nested Objects</button>
<button onClick={getArrayofObjects}>Array of Objects</button>
</div>
<ul>
{posts.map((item) => (
<li>{item.title}</li>
))}
</ul>
<ul>
{results ? (
<ul>
<li>NASA API RESULTS: {results}</li>
</ul>
) : (
<p>Loading...</p>
)}
</ul>
</main>
</div>
)
}
results is Object , no reactElement

Rendering nested json response data in react UI

This is the structure of the json being fetched. I am trying to render some of the nested threads data to a web page with react.
import react, {useState, useEffect} from "react";
import axios from 'axios'
import ReactJson from 'react-json-view'
const FeaturedBoards = () => {
const [boards, setBoards] = useState([{page: '', threads: {}}]);
useEffect(() => {
fetchBoards();
}, []);
const fetchBoards = () => {
axios.get('https://a.4cdn.org/po/catalog.json')
.then((res) => {
console.log(res.data);
setBoards(res.data);
})
.catch((err) => {
console.log(err);
});
};
if(boards === 0) {
return <div>Loading...</div>;
}
else{
return (
<div>
<h1>Featured Boards</h1>
<div className='item-container'>
{boards.map((board) => (
<div className='board' key={board.id}>
<p>{board['threads']}</p>
</div>
))}
</div>
</div>
);
}
};
export default FeaturedBoards;
I have tried everything to display some of the nested threads data but nothing comes up. I've tried doing a second call to map on board but no luck, storing it in a variable and calling from that still nothing. Am I doing something totally wrong?
I believe this is more fully answered by How can I access and process nested objects, arrays or JSON?. but to explain for this particular data structure, keep reading.
Look at your actual data... boards is an array. Each element in it is an object with page (int) and threads (array) properties. Each threads array element is an object with other properties. You can use map to iterate arrays and return a JSX representation of the objects within.
For example
const [boards, setBoards] = useState([]); // start with an empty array
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchBoards().then(() => setLoading(false))
}, []);
const fetchBoards = async () => {
const { data } = await axios.get('https://a.4cdn.org/po/catalog.json')
setBoards(data)
}
return loading ? <div>Loading...</div> : (
<div>
<h1>Featured Boards</h1>
<div className="item-container">
{boards.map(board => (
<div className="board" key={board.page}> <!-- 👈 note "page", not "id" -->
{board.threads.map(thread => (
<p>{thread.name}</p>
<p>{thread.sub}</p>
<p>{thread.com}</p>
<!-- etc -->
))}
</div>
))}
</div>
</div>
)

Infinite loop when using array map

Since I added
const dataList = dataSet.map(element => {
return <div>{element}</div>;
});
It has gone into an infinite loop but this line is also necessary for my program to display the notes so what can I do?
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Navbar from './Navbar';
function Notes() {
//This is just a string because we are sending just one
//might have to make it an array at some point
const [notes, setNote] = useState(String);
var dataArr = [];
const [dataSet, setDataSet] = useState([]);
const [dataList, setDataList] = useState();
useEffect(() => {
console.log('Use Effect Notes.js');
axios
.post('/api/user/notes')
.then(res => {
dataArr = res.data[0].notes;
//console.log(dataArr) ;
console.log(dataArr);
setDataSet(res.data[0].notes);
})
.catch(err => {
console.log('it didnt work' + err);
});
console.log('The array that i got ');
});
const dataList = dataSet.map(element => {
return <div>{element}</div>;
});
/*const taskList = task.map((object , index)=>{
return <div className='row justify-content-center'>
<h2>{object}</h2>
</div>
});*/
function noteDown(event) {
event.preventDefault();
var newNote = {
notes: notes,
};
console.log('note down ' + newNote);
axios
.post('/api/user/notes/add', newNote)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
}
function ffswork() {
console.log('ffswork');
}
return (
<div>
<div>
<h1> Notes </h1>
</div>
<form onSubmit={noteDown}>
<input
type="text"
placeholder="Note"
className="form-control"
value={notes}
onChange={e => {
setNote(e.target.value);
}}
/>
<input type="submit" value="AddNote" className="btn btn-primary" />
</form>
<button className="btn btn-primary" onClick={ffswork}>
getData
</button>
</div>
);
}
export default Notes;
I think you forgot to pass the array in the second argument from useEffect.
useEffect(() =>{
console.log("Use Effect Notes.js");
axios.post('/api/user/notes' ).then(res=>{
dataArr = res.data[0].notes ;
//console.log(dataArr) ;
console.log(dataArr);
setDataSet(res.data[0].notes);
}).catch(err=>{
console.log('it didnt work' + err);
});
console.log("The array that i got ");
}, []) // at this line
You can tell React to skip applying an effect if certain values
haven’t changed between re-renders.
So, if you want to make the request to get your data just once just after render, you just need to pass an empty array in the second argument of useEffect.
Read More here.

I do get a result from the back but can't assign it to a variable

i use axios to retrieve data from back.
I do get a result from the back. I can even console.log it. Yet, even I assign it to the recipe variable. It doesn't works. I get a empty array.
Anyone would known why ? I really don't understand.
FRONT
import React, { useEffect,useState } from 'react'
import Header from '../../components/Header'
import axios from 'axios'
export default function OneRecipePage(props) {
const [recipe, setrecipe] = useState([])
useEffect(() => {
const id = props.match.params.id
const getRecipe = async () => {
const url = `http://localhost:8000/user/recipe/${id}`
const result = await axios.get(url)
setrecipe(result.data)
console.log('recipe',recipe)
console.log('from back', result.data);
}
getRecipe()
},[])
return (
<div>
<Header/>
<main class="main">
<div class="heading">
<aside class="recipes-info__category_name">{recipe.name}
</aside>
<aside class="recipes-info__date">{recipe.created_at}
</aside>
<h2 class="heading-secondary heading-secondary--big">{recipe.title}</h2>
<p>by author</p>
</div>
<div class="image-box">
<img class="image" src={recipe.photo}/>
</div>
<div class="recipes-details"></div>
</main>
</div>
)
}
BACK
router.get('/recipe/:id', (req,res) => {
const id = req.params.id
connection.query('SELECT * from ETB.recipes WHERE id = ?', id, (err, results) => {
if (err) {
res.status(500).send('Error retrieving the recipe')
}else {
res.status(200).json(results)
}
})
})
In react, set State is an asynchronous aciton. By the time it executes next line, it is no guaranteed that it has set state.
instread of this
`setrecipe(result.data)
console.log('recipe',recipe)`
you can use useEffect() to detect the change in state.
useEffect(()=>console.log('recipe',recipe),[recipe])

array.forEach is not a function

I'm showing array of objects that is received from api call.I'm using react hooks.when I tried to iterate through array of objects it return foreach is not a function.
for this can I use await and async.
function searchArticle() {
const [articleList, setArticleList] = useState([]);
const { articleApiStatus, articleCurrentPage, searcArticle } = state;
useEffect(() => {
setArticleList(state.articleList);
articleListApiCall(articleCurrentPage);
}, [])
const articleListApiCall = (page = 1) => {
const isNewPage = articleCurrentPage !== page;
if (!articleApiStatus || isNewPage) {
getArticleList(page, '')
}
}
const getArticleList = async (page, searchkey) => {
const requestBody = {
page: page - 1, searchkey
}
await onArticleSearchPost(requestBody).then(articleresult => {
setArticleList(articleresult);
}).catch(err => {
console.log(err);
})
}
return (
<React.Fragment>
<div className="article-section">
<div className="search-result">
<Collapse >
{
articleList.forEach(element => {
<Panel header={element.article_title} key={element._id}>
<p>{element.article_body}</p>
</Panel>
})
}
</div>
<div className="pagination-section">
<Pagination defaultCurrent={1} total={50} />
</div>
</div>
</React.Fragment>
)
}
export default searchArticle;
Edit : I'm receiving following data from api call
[
{
"_id":"5d9efbc3b29b2876700adf6f",
"postedBy":"Admin",
"datePosted":"1570700227888",
"__v":0
}
]
First of all, do not use forEach to render JSX, it won't work. Use map instead:
<Collapse>
{articleList.map(element => { // use map
return (
<Panel header={element.article_title} key={element._id}>
<p>{element.article_body}</p>
</Panel>
);
})}
</Collapse>
Second, make sure that state.articleList, which you're setting to state in useEffect, is an actual array.

Resources