This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
Hi i am trying to show the data from a json url in ReactJS by inputing all the data to an array variable but i can't use the array in the JSX part because the array is not yet populated while rendering i try'd many things but i always end up in a promise loop where i need a promise to get the data from the other one.
THE CODE:
let arry = [];
let ar = [];
async function getdriver() {
const response = await fetch("https://ergast.com/api/f1/current/drivers.json");
ar = await response.json();
ar.MRData.DriverTable.Drivers.forEach((element) => {
arry.push(element);
});
return arry;
}
getdriver();
console.log(arry);// the array is populated but i think it waits for it before showing
console.log(arry.lenght); //lenght is 0
JSX:
return (
<div>
<Menu />
<div style={{ textAlign: "left" }}>
<h4>ff</h4>
<Button >change</Button>
<br></br>
<i>{arry[0].code}</i>// error ' Cannot read property 'code' of undefined ' so arry is empty?
</div>
</div>
);
Fetching data is a side-effect, and then you will need to store this data as state, so you will need to make use of two kinds of hooks (assuming you are creating function components):
useEffect
useState
Your asynchronous code will be called in useEffect, and when the call is completed you will save the result as the component's state using useState.
The code will look similar to the example below (I've kept as much as your code as possible, but renamed some functions and variables, and added some comments, to make this useful for as many other readers as possible):
import { useState, useEffect } from "react";
// this can exist outside the component
// it can even be in a different file
async function fetchDrivers() {
const response = await fetch(
"https://ergast.com/api/f1/current/drivers.json"
);
const data = await response.json();
return data.MRData.DriverTable.Drivers;
}
function YourComponent() {
// we declare the state, initially it's an empty array
const [drivers, setDrivers] = useState([]);
// we declare the effect that runs exactly once,
// when the component is mounted
useEffect(() => {
fetchDrivers().then(setDrivers);
}, []);
// we print the code for all drivers
// mapping the drivers array to JSX.
// notice the key attribute, this is required with map
// to uniquely identify each element
return (
<div>
<Menu />
<div style={{ textAlign: "left" }}>
<h4>ff</h4>
<Button>change</Button>
<br></br>
{drivers.map((driver, index) => (
<p key={index}>
<i>{driver.code}</i>
</p>
))}
</div>
</div>
);
}
When you want to show data that is fetched from a API on first render you should put the API call in useEffect and give an empty array as a dependency to useEffect while setting the array as a state value for example:
import {useState, useEffect} from 'React';
function YourComponent(){
const [array, setArray] = useState([]);
useEffect(()=>{getDriver().then((array)=>
{setArray(array)})}
,[])
}
this is just an example, in getDriver() after you get the result of the API call you should set array using setState() to tell React to re-render after that value was changed but here when you put it in useEffect it will only be triggered on first render.
Related
So I have a Notesdata array containing object with properties title, tagline, description. Notesdata array is stored in the "data" state variable in the App component and I am sending this data state variable to notes view component to populate on the UI but when I am appending a new object inside the data state variable and then sending it to notesview component, I am getting the error "Data.map is not a function".
When I am printing the "data" state variable I am getting an array but when I am checking its type it's showing "object", I am confused in why it is showing like that.
I also tried using Array.from() on the "data" state variable before passing it to notesview but that is also showing the same error.
------------App component------------------
import React, { useState } from "react";
import './App.css';
import Input from './Components/Input';
import Navbar from './Components/Navbar';
import Notesview from './Components/Notesview';
import Notesdata from "./Data/Notesdata";
function App() {
// const [data, setdata] = useState(Notesdata);
const [data, setData] = useState(Notesdata);
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id)
setData(newData)
}
function handlePost(value) {
let newval = data.push(value)
setData(newval)
console.log(typeof data)
console.log(data)
}
return (
<div className="App">
<header className="App-header">
<Navbar />
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview handleDelete={handleDelete} Data={data} />
</header>
</div>
);
}
export default App;
-----------------notesview component-----------------------
import React from 'react'
import Notescard from './Notescard'
import "../Styles/Notes.css"
const Notesview = ({ Data, handleDelete }) => {
return (
<>
<div className='notes'>
{
Data.map((item) => { // here is the Data.map where the error is coming
return <Notescard item={item} handleDelete={handleDelete} />
})
}
</div>
</>
)
}
export default Notesview
There's a lot wrong right here:
let newval = data.push(value)
setData(newval)
console.log(typeof data)
console.log(data)
Array.prototype.push returns the length of the array, so you're setting data to a number. (Which, incidentally, does not have a .map() function.)
You're mutating a state value (data) before trying to update it. Just update it.
You're trying to examine the value after it's been updated, but you're examining the current value and not the new value. State updates are asynchronous.
To update the state correctly, you'd do something more like this:
setData([...data, value]);
If you might have a batch of updates and you want each state update to use the updating state in the batch rather than the current state in the render, you could use the callback version of the state setter:
setData(d => [...d, value]);
This creates a new array reference, which includes all of the elements in the data array as well as the new value element, and sets that reference as the updated state. Without mutating the current state for the current render.
You get this error because Data is null. you can check Data's existence before trying to map on it in Notesview like this:
Data && Data.map(...)
This question already has answers here:
Arrow function VS normal function in Array.map()
(2 answers)
Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?
(4 answers)
Closed 12 months ago.
I'm pretty new to hooks and I'm trying to use setState for data I'm getting back from an API, but the state never updates. I only need to make the call when the component mounts, which is why the second argument in my useEffect is an empty array. I can see that I'm getting back the data when I console.log the response, it just isn't being set.
const [routeOptions, setRouteOptions] = useState()
useEffect(() => {
Axios.get("https://svc.metrotransit.org/NexTrip/Routes?format=json").then(response => {
const routes = response.data
setRouteOptions(routes)
});
}, []);
I then try to map through the data like so:
{routeOptions && routeOptions.map(option => {
<option>
{option.description}
</option>
})}
but because my state never got set there's nothing to map through.
I may be missing something super obvious cause I'm not familiar with hooks, so any help is appreciated!
You need to return a value from your .map(). Make sure to give your <option> a unique key prop as well.
Note also that the route property Description has a capital D :)
<select>
{routeOptions.map((option) => {
return <option key={option.Route}>{option.Description}</option>;
})}
</select>
here it is all together
import { useState, useEffect } from "react";
import Axios from "axios";
export default function Test() {
const [routeOptions, setRouteOptions] = useState(null);
useEffect(() => {
Axios.get("https://svc.metrotransit.org/NexTrip/Routes?format=json").then(
(response) => {
const routes = response.data;
setRouteOptions(routes);
}
);
}, []);
if (!routeOptions)
return (
<div>
<p>Loading...</p>
</div>
);
return (
<select>
{routeOptions.map((option) => {
return <option key={option.Route}>{option.Description}</option>;
})}
</select>
);
}
try to use Async-select https://react-select.com/async and make a function that returns an array of pair {label, value} instead of wasting your time stuck here.
I'm having a problem mapping through an array with objects, and I can't find what problem is, but I asume its because of async, but I want you to take a look at it.
I'm getting two error messages and I don't know if they relate:
TypeError: Cannot read property 'map' of null
1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
import {useState, useEffect} from 'react';
// import styled from 'styled-components';
export default function Admin() {
const [quotes, setQuotes] = useState(null);
const get_all_quotes = async () => {
const {data, error} = await supabase
.from('quotes_en')
.select('quote')
console.log(data);
if (error) console.table(error)
setQuotes(data)
}
useEffect(() => {
get_all_quotes()
}, [])
return (
<div>
{quotes.map(({id, quote}) => {
return <p key={id}>{quote}</p>
})
}
</div>
)
}
Issue
The initial quotes state value is null, so it can't be mapped.
const [quotes, setQuotes] = useState(null);
Solution
Provide valid initial state, I suggest using an empty array ([]).
const [quotes, setQuotes] = useState([]);
Now you'll have valid quotes state that can be mapped on the initial render. Array.prototype.map can safely handle empty arrays.
{quotes.map(({id, quote}) => {
return <p key={id}>{quote}</p>
})}
As #DrewReese said you can either set the initial value of state as an empty array or choose to show quotes conditionally using:
{quotes && quotes.map(({id, quote}) => {
return <p key={id}>{quote}</p>
})
What this code does is that it will check whether any value is there for quotes and if it's there only, it will call the quotes.map() function.
Update:
We can also use optional chaining, which is more readable than conditional (short-circuiting as in above).
{quotes?.map(({id, quote}) => {
return <p key={id}>{quote}</p>
})
What the above code does is that it checks whether quotes are there and if it's there only, it will call the map function. If "quotes" is undefined, or null or any other falsy value, it will return the falsy value and stops execution.
The advantage of these solutions is that even if, in any case, quotes are not having an array it will not cause any issue, but won't execute that code.
Thanks to #DrewReese for this solution.
I want to create a function that maps throw a firestore collection and return a Component for each document in the collection I have tried to use the code below
<div className="posts">
{
db.collection("posts")
.get()
.then((snapshot) => {
snapshot.docs.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />
))})}
</div>
but this error shows:
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.ode
JSX is not the place to make Ajax calls. Instead make the call inside componentDidMount/useEffect and set the data received inside state. As soon as state is updated with received data, React will automatically re-render the component and display the expected content. Try something as follows:
const [snapshots, setSnapshots] = useState();
useEffect(()=> {
db.collection("posts")
.get()
.then((snapshot) => {
setSnapshots(snapshot.docs)
}
)
}, []);
render
<div className="posts">
{snapshots && snapshots.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />
)
}
</div>
Since you haven't given a hint as to whether you're using hooks or not, here is how you should deal with asynchronous stuff and setting states in React (using hooks) :-
function MyComponent () {
const [snapshot,setSnapshot] = useState();
useEffect(()=>{
async function getPosts(){
let snapshot = await db.collection("posts").get();
setSnapshot(snapshot);
}
getPosts();
},[])
return
(<div className="posts">
snapshot?.docs?.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />)
</div>)
}
What you are doing is returning Promise object as the error states. That async stuff is taken care either inside a useEffect,event handler or a custom-hook. There could be more ways but this is a general point to start.
As the error message states Promise is not a valid React child. To solve that issue you can introduce a state which keeps the result of db.collection('posts').get() method. Then iterating through on that array with your .map() which will be a valid React child.
See a possible solution for your scenario:
// in the root of you component, introduce a state
const [posts, setPosts] = useState([])
// here in useEffect you can do your API call and update the state
useEffect(() => {
db.collection("posts")
.get()
.then((snapshot) => {
setPosts(snapshot.docs)
})
}, [])
// ... rest of your component's code
return (
<div className="posts">
{
posts && posts.map((doc, i) => (
<div key={i}>
doc.data().title
</div>
// here you can use <PostContect /> component
// either doc.data().title or doc.title to get the value
))
}
</div>
)
Suggested reads:
Using the Effect Hook
Using the State Hook
The code you wrote returns a promise, which can't be rendered by react. You can use conditional rendering to return something else while the data is being fetched.
Check this question for more info
I have a component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()