I am making a rather simple API call to (https://api.punkapi.com/v2/beers) and displaying fetched data in the component, but somehow the data is not getting displayed on the page, but when I console log it the data is displayed correctly.
const Comp= () => {
const [item, setItem] = React.useState([]);
React.useEffect(() => {
fetch('https://api.punkapi.com/v2/beers')
.then((res) => res.json())
.then((data) => {
setItem(data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div>
{item.map((beer) => {
const { name, tagline } = beer;
<p>{name}</p>;
console.log(name);
})}
</div>
);
};
Issue
You don't return anything from your item mapping. Return the JSX you want to render (i.e. return <p>{name}</p>) and don't forget to add a React key to the mapped elements. Below I've simplified to an implicit return.
You should also remove the console log from the render return as "render" methods are to be pure functions without side-effects, such as console logging. Either log the fetch result in the promise chain or use an useEffect hook to log the updated state.
useEffect(() => console.log(item), [item]);
...
return (
<div>
{item.map(({ id, name }) => (
<p key={id}>{name}</p>
))}
</div>
);
You need to return the value from inside .map method like:
return (<p>{name}</p>)
You need to return the JSX elements in your map.
const Comp= () => {
const [item, setItem] = React.useState([]);
React.useEffect(() => {
fetch('https://api.punkapi.com/v2/beers')
.then((res) => res.json())
.then((data) => {
setItem(data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div>
{item.map((beer) => {
const { name, tagline } = beer;
return <p>{name}</p>;
console.log(name);
})}
</div>
);
};
Related
I am trying to pass a user address into this Fetch Function, set the value of a state variable equal to the address, and then use that address to make an api call. But as expected, everything runs at the same time and the api call fails because it does not receive the user address.
I am relatively new to useEffect, the below is how I assume a function like this should be written, but evidently I am missing something. It does not return any errors, just a undefined value in the log statement I have below.
const Fetch = (props) => {
const api_key = process.env.REACT_APP_API_KEY;
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
async function Get(){
setAddr(props.useraddress)
}
Get();
}, []);
async function GetNFT() {
useEffect(() => {
axios
.get(
`https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
)
.then(res=> {
setData(res.data.nfts);
})
.catch(err=> {
console.log(err);
})
},[]);
}
GetNFT();
console.log(data);
return (
<div>
<script>{console.log('Fetch'+addr)}</script>
{/*
<>
{data.map((dat,id)=>{
return <div key={id}>
<FetchData NFTData={dat} />
</div>
})}
</>
*/}
</div>
)
}
You need a single useEffect that would depend on useraddress that you can destructure from the props, and make an api call that uses the useraddress. You don't need to store useraddress in the state.
const api_key = process.env.REACT_APP_API_KEY
const createUrl = addr => `https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
const Fetch = ({ useraddress }) => {
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
axios.get(createUrlcreateUrl(useraddress))
.then(res=> {
setData(res.data.nfts)
})
.catch(err=> {
console.log(err)
})
}, [useraddress])
console.log(data)
return (
// jsx
)
}
Note that the useEffect would be triggered on component's mount, and whenever useraddress changes. If useraddress might be empty or undefined when the component mounts, add a condition inside that avoids the call:
useEffect(() => {
if(!useraddress) return // skip the api call if the address is empty/undefined/null
axios.get(createUrlcreateUrl(useraddress))
.then(res => {
setData(res.data.nfts)
})
.catch(err => {
console.log(err)
})
}, [useraddress])
I am playing around with an API that gets a list of Pokemon and corresponding data that looks like this.
export function SomePage() {
const [arr, setArray] = useState([]);
useEffect(() => {
fetchSomePokemon();
}, []);
function fetchSomePokemon() {
fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
.then(response => response.json())
.then((pokemonList) => {
const someArray = [];
pokemonList.results.map(async (pokemon: { url: string; }) => {
someArray.push(await fetchData(pokemon))
})
setArray([...arr, someArray]);
})
}
async function fetchData(pokemon: { url: string; }) {
let url = pokemon.url
return await fetch(url).then(async res => await res.json())
}
console.log(arr);
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div
key={index}
>
{pokemon.name}
</div>
))
}
</div>
);
}
The code works(kind of) however on the first render the map will display nothing even though the console.log outputs data. Only once the page has been refreshed will the correct data display. I have a feeling it's something to do with not handling promises correctly. Perhaps someone could help me out.
TIA
Expected output: Data populated on initial render(in this case, pokemon names will display)
The in-build map method on arrays in synchronous in nature. In fetchSomePokemon you need to return a promise from map callback function since you're writing async code in it.
Now items in array returned by pokemonList.results.map are promises. You need to use Promise.all on pokemonList.results.map and await it.
await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
return fetchData.then(someArray.push(pokemon))
}));
On your first render, you don't have the data yet, so arr[0] doens't exist for you to .map on it, so it crashes. You need to check if the data is already there before mapping.
Using optional chaining, if there's no data it will not throw an error on your first render and it will render correctly when the data arrive and it re-renders.
...
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div key={index}>{pokemon.name}</div>
))}
</div>
);
}
in
useEffect(() => { fetchSomePokemon(); }, []);
[] tells react there is no dependencies for this effect to happen,
read more here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
One way to solve your issues is to await the data fetching in useEffect().
Here's a POC:
export function Page() {
const [pokemon, setPokemon] = useState([]);
// will fetch the pokemon on the first render
useEffect(() => {
async function fetchPokemon() {
// ... logic that fetches the pokemon
}
fetchPokemon();
}, []);
if (!pokemon.length) {
// you can return a spinner here
return null;
}
return (
<div>
{pokemon.map(item => {
// return an element
})}
</div>
);
}
I've done a API call to get some datas then store it in an array and do a .map in the return
This is the code if you guys have any ideas it's been 2 hours that i'm stuck on this :(
import {useEffect, useState} from 'react';
import {useParams} from "react-router-dom";
import axios from "axios";
const CharacterScreen = () => {
const params = useParams()
const [character, setCharacter] = useState([]);
const [starships, setStarships] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/?search=${params.character}`)
.then((r) => {
setCharacter(r.data.results[0])
getStarships(r.data.results[0])
})
.catch((e) => console.log(e))
const getStarships = (data) => {
let array = []
data.starships.forEach(element => {
axios.get(element)
.then((r) => {
array.push(r.data)
})
.catch((e) => console.log(e))
})
console.log(array)
setStarships(array)
}
}, []);
console.log(starships)
return (
<div>
<p>{character.name}</p>
<p>{character.eye_color}</p>
<p>{character.birth_year}</p>
<p>{character.gender}</p>
<p>{character.created}</p>
<p>{character.edited}</p>
{starships.map((element) => {
console.log('ok')
return (
<p key={element.key}>{element.name}</p>
)
})}
</div>
)
}
This is the .log of starships :
This is my return :
Any help would be apréciated
Use spread operator :
useEffect(() => {
axios.get(`https://swapi.dev/api/people/?search=${params.character}`)
.then((r) => {
setCharacter(r.data.results[0])
getStarships(r.data.results[0])
})
.catch((e) => console.log(e))
const getStarships = (data) => {
let array = []
data.starships.forEach(element => {
axios.get(element)
.then((r) => {
array.push(r.data)
})
.catch((e) => console.log(e))
})
setStarships([...array]) <=== //spread opeator
}
}, []);
The code inside your forEach will run asynchronously. You would have to wait for all that data to be actually populated in your array. async/await pattern + Promise.all(..) would be a good bet here and can be done like so :-
const getStarships = async (data) => {
let array = await Promise.all(data.starships.map(element => {
return axios.get(element)
.then((r) => {
return r.data
})
.catch((e) => console.log(e));
}))
setStarships(array);
}
Currently in your code by the time you do setStarships(array), array will be empty i.e. [].
Check this codesandbox where it's working :-
Note :- Don't pay attention to element.replace code, thats just for making secure requests
You have a syntax error, you should replace your bracket with a parenthesis like following:
{starship && starships.map((element) => (//here
console.log('ok')
return (
<p key={element.key}>{element.name}</p>
)
)//and here)}
I have the following code :
export default function ProjectView({filteredProjects,revenueGroups}) {
const [projects,setProjects] = useState();
useEffect(() => {
const aap = filteredProjects.map(filteredObject => {
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
})
return filteredObject;
})
setProjects(aap);
},[filteredProjects])
And the rendered component :
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
{console.log(project)}
</div>
))}
</div>
)
This works fine , when i console.log(project) like above it shows the following :
{
projectName: "privaye"
rekt: "project:1b1126ebb28a2154feaad60b7a7437df"
__proto__: Object
}
when i console.log(projectName) it shows the name, but when i console.log(project.rekt) it's undefined...
eventhough its there when i console.log(project)
EDITED
I didn't notice the was a promise inside :P
useEffect(() => {
fetchThings()
},[filteredProjects])
const fetchThings = async () => {
const promArr = filteredProjects.map(filteredObject => {
return getProjectFields(filteredProject.project.id)
})
const filteredObjects = await Promise.all(promArr)
const projectsMod = filteredObjects.map(fo => ({
...fo,
rekt: fo.data[0].id,
}))
}
Maybe an approach like this will help you with the asyncronous problem
console.log isn't rendering anything so maybe React doesn't try to refresh this part of the DOM when projects state is updated.
You can stringify the object to check the content of the variable inside the return
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
<span>{ JSON.stringify(project) }</span>
</div>
))}
</div>
)
You are running promise, after this promise will return value you set rekt, but promise will work god knows when, and most probably you check value before this promise resolved.
getProjectFields(filteredObject.projectId).then(res => {
// THIS CODE CAN RUN AFTER RENDERING
filteredObject.rekt = res.data[0].id;
})
so first you wait till promises will complete and only then set new state
const ourPreciousPromises = filteredProjects.map(filteredObject =>
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
return {...filteredObject}
})
})
Promise.all(ourPreciousPromises).then(newObjects =>
setProjects(newObjects)
)
I am using a useEffect hook to make an API call depending on data from a photos prop being passed in.
const ImageCarousel = ({ src, photos }) => {
const [photoList, setPhotos] = useState([]);
const storageRef = firebase.storage().ref();
console.log("photos prop:", photos);
const getImage = photoId => {
return new Promise((resolve, reject) => {
storageRef
.child("images/" + photoId)
.getDownloadURL()
.then(url => resolve(url))
.catch(error => reject(error));
});
};
useEffect(() => {
console.log("trigger");
Promise.all(
photos.map(async photoId => {
const url = await getImage(photoId);
return url;
})
)
.then(photoUrls => {
setPhotos(photoUrls);
})
.catch(e => console.log(e));
}, [photos]);
return (...)
}
I have passed the photos prop into the dependency array of my useEffect hook, so it should fire when the photos prop changes. I can see that the prop is indeed changing in the console, but my useEffect hook does not fire when the prop change comes through. This is what appears in my console:
Why is the useEffect not firing twice? From my understanding it should fire when the component renders the first time, and fire again every time the photos prop changes.
Try to make sure your photos prop is immutable,
meaning, you send a new array each time there is a change and not mutating the array
This may not be the most correct answer, but I've tried this and it works. Hat tip to https://dev.to/stephane/how-to-make-sure-useeffect-catches-array-changes-fm3
useEffect(() => {
console.log("trigger");
Promise.all(
photos.map(async photoId => {
const url = await getImage(photoId);
return url;
})
)
.then(photoUrls => {
setPhotos(photoUrls);
})
.catch(e => console.log(e));
}, [JSON.stringify(photos])); // stringify the array used as the trigger, and it'll catch the change
useEffect will update if you pass the new data, find the sample code below and check the console.log.
import React, { useState, useEffect } from "react";
const EffectCheck = ({value}) => {
console.log('Component Trigger', value);
useEffect(() => {
console.log('Updated Effect', value);
},[value]);
return (
<div>{value}</div>
)
}
export default function() {
const [checkValue, setCheckValue] = useState(Math.random());
const [dynamicCheckValue, setdyamicCheckValue] = useState(Math.random());
return (
<>
<div><h3>No Update</h3>
<EffectCheck value={checkValue}/>
</div>
<div><h3>New Update</h3>
<EffectCheck value={dynamicCheckValue}/>
</div>
<button onClick={() => setCheckValue('Math.random()')}> No Update</button>
<button onClick={() => setdyamicCheckValue(Math.random())}> New Update</button>
</>
);
}