I want to use useEffect(on mount) to fetch from API and store it in useState. Fetch API is used to get the data. The problem is when initial page loading and also when I reload the page, it outputs an error called test.map is not a function. Why this happening and how to avoid this ?
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState({})
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
You can't map on an object {}, so you should need to define an array [] for the base state :
const[test, setTest] = useState([])
You have to change {} to array first to be able to map over it. You can easily place ? after test like this. or make in the default value of the state a default value for item name. because this error results as you map over an empty object.
import { useEffect, useState } from 'react';
function App() {
const[test, setTest] = useState([{name:"default"}])
useEffect(() => {
testfunc()
}, [])
async function testfunc(){
let api = await fetch('https://jsonplaceholder.typicode.com/users')
let apijson = await api.json()
setTest(apijson)
}
return (
<div className="App">
{
test?.map((item) => {
return(
<div>
{item.name}
</div>
)
})
}
</div>
);
}
export default App;
As already mentioned, you can't use the .map for objects.
Instead of this, you can make something like that
Object.keys(test).map(key => {
const currentSmth = test[key]
return(
<div>
{currentSmth.name}
</div>
)
})
})
I think it helps you to solve your problem.
Be careful using the correct data structures and methods.
Related
This is my first time using react-query and I having problem request data from API.
This is the code:
import { useQuery } from "#tanstack/react-query";
import axios from "axios";
import "./App.css";
function App() {
const fetchData = async () =>{
const { data } = await axios.get("https://api.jikan.moe/v4/anime");
const response = data.data;
return response
}
const { isLoading,data,isError } = useQuery(
["posts"],
fetchData,
{
retry: false,
refreshInterval: 0,
staleTime: 0
}
);
if (isLoading) return <h1>...Loading</h1>;
if (isError) return <h1>Fail</h1>;
console.log(data)
return (
<div className="App">
{data.map((datas)=>{
<div>
{datas.title}
</div>
})}
</div>
);
}
export default App;
when I console.log(data) it does show the list of data Array of 25 object element.
But when I try the use data.map to show the data in the website nothing appear but It does appear if I only try the show one of the array like
{data[0].title}
the data does appear but when I try the show all array element with data.map but nothing seems to appear. Can someone help me?
thanks
the data does appear but when I try the show all array element with data.map but nothing seems to appear. Can someone help me?
Your Array#map does not return anything.
{data.map((datas) => {
return ( // return the JSX
<div>
{datas.title}
</div>
);
})}
I need to add sorting to fetched data (ascending/descending).
I get all the data from API endpoint. I map every object in that array to be displayed in separate component card. But once I choose to sort data from Descending name I get a quick change of components were they are sorted from Z to A but it just instantly converts back to initial fetched state (from A to Z).
Could you please tell me where the problem is? I don't know why but it feels like sorted array doesn't get saved in state "data" which I use to map all the cards.
import { useState } from 'react';
import { useEffect } from 'react';
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState('default');
useEffect(() => {
fetchData();
sortData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
setData(data);
};
function sortData() {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
return (
<div className='content'>
<header className='content__header'>
<h1>Header placeholder</h1>
</header>
<div className='wrapper'>
<div className='wrapper__sort-buttons'>
<select
defaultValue='default'
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value='default'>
Sort by
</option>
<option value='ascending'>Ascending</option>
<option value='descending'>Descending</option>
</select>
</div>
<ul className='wrapper__list'>
{data.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
This is what I get just for a quick moment:
And then it just goes back to initial state:
It appears the way you're using useEffect is causing your component to refetch the data each time you change the sort type. This could be causing a race condition due to multiple places updating your data state at different times.
I would move the sorting logic into a useMemo and only fetch the data in useEffect on initial load:
import { useEffect, useMemo, useState } from "react";
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from "uuid";
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState("default");
// Move sort logic here...
const sortedData = useMemo(() => {
let result = data;
if (sortType === "descending") {
result = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === "ascending") {
result = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
return result;
}, [data, sortType]);
// Only fetch data once on component mount...
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch(
"https://restcountries.com/v2/all?fields=name,region,area"
);
const data = await response.json();
setData(data);
};
return (
<div className="content">
<header className="content__header">
<h1>Header placeholder</h1>
</header>
<div className="wrapper">
<div className="wrapper__sort-buttons">
<select
defaultValue="default"
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value="default">
Sort by
</option>
<option value="ascending">Ascending</option>
<option value="descending">Descending</option>
</select>
</div>
<ul className="wrapper__list">
{/* Use sortedData here instead of data... */}
{sortedData.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
Here's a basic example in a Codesandbox (I commented out your styles/card component): https://codesandbox.io/s/goofy-tdd-8lio9?file=/src/App.js
This might be happening for the reason that set state function is asynchronous in nature and the order in which setData is being called is different than you expect.
So, for the initial call with sortType 'default', you are not noticing any change as you are returning the data as it is. But once you change it to 'descending', setData() from sortData() is called earlier than that from fetchData() so as you have already data in your state, you see a change in data in UI for few moments, but then setData() from the function fetchData is called and replaces your data with the one you got from the API call which is unsorted or in ascending order.
POSSIBLE SOLUTION
DON'T set the state inside fetchData method, rather just set it once inside the sortData method, as you are needing it anyhow.
So your code will look something like this:
// we will call sortData inside fetchData so remove it from here
useEffect(() => {
fetchData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
// using API response data as an input to sortData function
sortData(data)
};
// using data from parameter instead of state
function sortData(data) {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
IMPROVEMENT
Your API call is not depending upon the SORTING ORDER, so you don't need to call the API again and again, just call it once, and then sort the data on the value changed from dropdown.
// call the API on initial load only
useEffect(() => {
fetchData();
}, []);
// and on sortType change you can handle it like this:
useEffect(() => {
sortData(data);
}, [sortType]);
// and using this approach you can use the exact same code for both functions implementation that you posted in your question above.
I'm trying to understand somebody else code,
I have this component:
import React from 'react';
import { useEffect, useState } from 'react';
export default function CountriesList({ searchValue }) {
const [data, setData] = useState([])
//Onmount
useEffect(() => {
async function init() {
//API Calls- request data from the server
const response = await fetch('https://restcountries.com/v2/all');
const body = await response.json();
setData(body);
}
init()
}, [])//dependencies array
return (
<div className="countries-container">
{data
.filter(country => country.name.toLowerCase().includes(searchValue.toLowerCase()))
.map((country) => {
const { name, flag } = country;
return (
<div key={name} className="country-container">
<h3 className="title">{name}</h3>
<img src={flag} height="100px" width="100px" alt="flag" />
</div>
)
})}
</div>
)
}
inside init(), the programmer call init() again, can you explain why?
I tried to look for this style of programming and I didn't find anything.
whiteout this line the API call doesn't work.
thank you!
I may be mistaken, but as far as I can see, init function is declared and called right after declaration.
Check this out: https://github.com/facebook/react/issues/14326
I have the next situation in my react application:
export default function App() {
const arr = []
useEffect(() => {
arr.push('test')
},[])
console.log(arr)
return (
<div className="App">
{
arr.map((i) => <span>{i}</span>)
}
</div>
);
}
Above i try to push a new item in the array. So in the console.log(arr) i expect to have ['test']. Why it does not happen? demo: https://codesandbox.io/s/reverent-mayer-pxswq?file=/src/App.js:59-294
useEffect runs in the next frame, so it won't be populated until later.
You should try this:
const arr = []
useEffect(() => {
arr.push('test')
console.log(arr)
},[])
You also don't want to be setting state like that, use useState.
const [arr, setArr] = useState([])
useEffect(() => {
setArr(a => [...a, 'test'])
}, [arr])
You should use the state hook for your arr.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
setArr([...arr, "test"]);
}, []);
console.log(arr);
return (
<div className="App">
{arr.map((i) => (
<span>{i}</span>
))}
</div>
);
}
The code you shared is working but the console.log is not at the good place.
Your console.log is login before the useEffect();
export default function App() {
const arr = []
useEffect(() => {
arr.push('test');
console.log('After UseEffect', arr);
},[])
console.log('Before UseEffect',arr)
return (
<div className="App">
{
arr.map((i) => <span>{i}</span>)
}
</div>
);
}
In your case, useEffect run after console.log so arr still empty when log,
Agree with #Stoobish, but:
Tracking arr change should use useDebugValue or log inside useEffect
If you want to prevent duplicate render when state change, use useEffectLayout instead of useEffect.
import { useEffect, useLayoutEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([]);
useLayoutEffect(() => {
setArr([...arr, "test"]);
}, []);
useEffect(() => {
console.log(arr);
}, [arr])
return (
<div className="App">
{arr.map((i) => (
<span key={i}>{i}</span>
))}
</div>
);
}
You should consider three point below:
You should use useState hook.
So Use
const [arr, setArr] = useState([]);
Not
const arr = []
When you use React you must consider useState because when you update any variable using useState Hook then React will update the state and can be able to rerender your component. Otherwise, your component will not be updated.
You should not push directly into the array arr.
So Use
setArr(item => [...item, newItem])
Not
arr.push('test')
As I explain it above, if you dont use setArr then React will not update your component.
You should use key when you use multiple html element using map function.
So Use
{arr.map((a, i) => (
<span key={i}>{a}</span>
))}
Not
arr.map((i) => <span>{i}</span>)
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
Here is the complete example:
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
let newItem = 'test';
setArr(item => [...item, newItem]); // Here we are using `spread operator` to use previous items from `arr`
console.log(arr);
}, []);
return (
<div className="App">
{arr.map((a, i) => (
<span key={i}>{a}</span>
))}
</div>
);
}
I connected my react app to firebase and I think the problem is that the page loads before the data from my database is acquired, what can I do to delay the function until after it finishes acquiring the data?
function getPosts(){
db.collection("Posts").get().then(snapshot =>{
snapshot.docs.forEach(docs =>{
createPost(
docs.data().postName,
docs.data().createdAt,
docs.data().postContent,
)
})
})
}
getPosts();
function Blog(){
return (
<div>
<Navbar/>
<div className="container">
<div className="row" id="posts-collection">
</div>
</div>
</div>
)
}
export default Blog;
As MehmetDemiray already shows you can load data as an effect within a function component, but that answer assumes you only wish to track loading status.
If you want to use the data loaded to display the post data then you will also need to store the returned data.
const Blog: React.FunctionComponent = () => {
// state to store data returned by async call. (originally set to null).
const [posts, setPosts] = React.useState(null);
// use an effect to load async data.
// the effect only runs on component load and every time the data in
// the dependency array changes "[setPosts]" (reference comparison).
React.useEffect(() => {
// Create funtion to run async logic to load posts.
const getPosts = () => {
// load posts
db.collection("Posts").get().then(snapshot => {
// map loaded posts to an array of easy to manage objects.
const loadedPosts = snapshot.docs.map(docs => {
return {
name: docs.data().postName,
createdAt: docs.data().createdAt,
content: docs.data().postContent,
}
});
// store loaded posts in state.
setPosts(loadedPosts ?? []);
});
};
// run async function created above.
getPosts();
}, [setPosts])
// posts will remain null until the async function has loaded data.
// you can manually track loading in a separate state if required.
if (posts === null) {
// Show loading view while loading.
return (
<div>
Loading Posts...
</div>
);
}
// map out posts view after posts have been loaded.
return (
<div>
{posts.map(post => (
<div>
<div>{post.postName}</div>
<div>{post.createdAt}</div>
<div>{post.content}</div>
</div>
))}
</div>
);
};
You need to loading control before rendering jsx. Looks like this;
import {useEffect, useState} from 'react';
function Blog(){
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
function getPosts(){
db.collection("Posts").get().then(snapshot =>{
snapshot.docs.forEach(docs =>{
createPost(
docs.data().postName,
docs.data().createdAt,
docs.data().postContent,
)
})
setLoading(false);
})
}
getPosts();
}, [])
return (
loading ?
<div>
< Navbar/>
<div className="container">
<div className="row" id="posts-collection">
</div>
</div>
</div> : null
)
}
export default Blog;