Cannot show images after fetching from an API - reactjs

I'm trying to chain two fetch request using axios. My code is :
const fetchCatsData = async () => {
const fetchBreeds = await axios.get("https://api.thecatapi.com/v1/breeds", {
headers: {
"x-api-key": "MY API KEY ",
},
})
await fetchBreeds.data.map(breed => {
axios.get(`https://api.thecatapi.com/v1/images/search?breed_ids=${breed.id}&include_breeds=false`)
.then(res => breed.image_url = res.data[0].url)
})
dispatch({ type: FETCH_BREEDS, payload: fetchBreeds.data })
It succeeds and in react dev tools , i see a special key called 'image_url'inside my context , with the url of the image.I click on it's value and it open the requested image.
But when i'm trying to show the image in an image HTML tag , it shows nothing ...
Am i missing something ?
Thanks in advance

From the code here, i guess you are trying to wait until the image_url is set for all items in fetchBreeds.data. But it won't work in that way.
await fetchBreeds.data.map(breed => {
axios.get(`https://api.thecatapi.com/v1/images/search?breed_ids=${breed.id}&include_breeds=false`)
.then(res => breed.image_url = res.data[0].url)
})
You use await on the map function. The map function is not async, so when you dispatch the action, the image_urls are not set yet. When you checked the store and found the image_url was there is because the fetchBreeds.data was mutated by axios call directly without using redux dispatch system. This didn't trigger the UI re-render so image didn't show. What happened is shown below:
dispatch({ type: FETCH_BREEDS, payload: fetchBreeds.data }) This happens first.
Component is notified so re-render. image_url is not set yet, so image is empty
.then(res => breed.image_url = res.data[0].url) This is called next. Because the function holds the reference of the fetchBreeds.data, so it changes the fetchBreeds.data object directly without using reducer.
UI is not notified so it doesn't know image_url is changed and won't re-render.
I suggest you change the function to:
await Promise.All(fetchBreeds.data.map(breed => {
return axios.get(`https://api.thecatapi.com/v1/images/search?breed_ids=${breed.id}&include_breeds=false`)
.then(res => breed.image_url = res.data[0].url)
}))

Related

FullCalendar events (as a json feed) is getting re-fetched after eventClick and dayClick

I am using FullCalendar in a React page and in order to get the events I am using events as a json feed (https://fullcalendar.io/docs/events-json-feed). The data gets loaded fine however when I use eventClick or dateClick to open a modal, FullCalendar is refreshing and another POST request is sent to the backend.
Is there a way to prevent that? I want to avoid sending unnecessary requests...
Also, as the data gets refreshed the calendar events are re-drawn and this causes to look like a glitch. Similar to this:
https://user-images.githubusercontent.com/3365507/85287154-6fc61c00-b4c6-11ea-83c1-cb72a3aec944.gif
Here are a few examples of the code I am using:
<FullCalendar
...
eventClick={handleEventClick}
dateClick={handleDateClick}
eventSources={[
{
events: fetchEvents,
failure: function() {
console.log('ERROR');
}
},
]}
...
/>
And fetchEvents is something like this:
const fetchEvents = (fetchInfo, successCallback, failureCallback) => {
fetch('http://localhost/calendarEvents', {
method: 'POST',
body: JSON.stringify(fetchInfo),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((data) => {
const parsedEvents = [];
for (const event of data) {
parsedEvents.push({
...event,
start: moment(event.startAt).toDate(),
end: moment(event.endAt).toDate(),
title: event.title
});
}
successCallback(parsedEvents);
})
.catch((error) => {
failureCallback(error);
});
}
and handleEventClick:
const handleEventClick = (event) => {
setSelectedEvent(event);
setOpenEventModal(true);
};
--EDIT--
Here is a CodeSandbox example:
https://codesandbox.io/s/suspicious-murdock-4jfept?file=/src/App.js
You can see at the Console tab that a new fetch is tried each time you click at a date to open the Modal. A new fetch is expected only when switching months in the calendar because I am using eventSources json feed option. But if it was already fetched it shouldn't do it again just by opening the Modal.
setSelectedEvent(event);
setOpenEventModal(true);
If state changes in <FullCalendar> it will rerender. This may be causing it to call for the data again.
Either stop changing the state in FullCalendar, do your API calls outside and pass in the data, or don't call for the data on every render.
What is the full code for <FullCalendar>?

Can't access state in functional component in Reacht Native

I'm currently trying to build an app in React Native. Unfortunately, I'm having a hard time understanding the state management in functional components.
The fetch resolves successfully and gives me an array of activities and I store them in the component's state. But after that I want to make more fetches regarding these activities and for that i need to know each activities' ID, so I have to access the state. This doesn't work though, as there's only an empty array printed to the console from the last log.
From the printed timestamps I can see that everything executes in the desired order and I can easily access the state in other places and get the full array of activities, but why isn't it working here?
Here's the code:
const [activities, setActivities] = useState([]);
async function getActivites(cred){
const zeroLevel = Date.now();
fetch(`https://www.strava.com/api/v3/athlete/activities?access_token=${cred.access_token}`)
.then((res) => res.json())
.then((data) => {
for (const element of data) {
setActivities(oldActivities => [... oldActivities, element])
console.log(Date.now() - zeroLevel)
}
console.log('for-loop finished', Date.now() - zeroLevel)
})
.then(() => console.log(Date.now() - zeroLevel))
.then(() => console.log(activities))
}
I already tried to store the array in another object to make it more easily accessible, but I'm almost certain there's an easier way.
If data is an array, you don't need to iterate over it, you can just set the activites with data, instead of looping over it:
.then((data) => {
setActivities(data)
console.log('fetch finished', Date.now() - zeroLevel)
return data
})
.then((data) => {
data.map(activity => // do the fetch on each activity)
}
Or if you want to base the chained fetch on the state, then you can manually observe the change like this:
.then((data) => {
setActivities(data)
console.log('fetch finished', Date.now() - zeroLevel)
})
useEffect(() => {
activities.map(activity =>// do the fetch on each activity)
},[activities])

How can I optimize my code to stop sending GET requests constantly?

I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])

How do I trigger a rerender in react?

The idea is that I select an image which then gets uploaded to mongodb and in the same time as I press on upload button I want to trigger rerendering of the page so that the avatar gets updated with the newest image. To accomplish that I first send a PUT request t the server and right after that I set a local storage obejct with the values of the response.
The functions that capture and upload the image
const [reload, setReload] = React.useState(false);
React.useEffect(() => {}, [reload])
const fileSelectedHandler = evt => setAvatarImage(evt.target.files[0]);
const uploadProfileImage = async evt => {
evt.preventDefault();
const formData = new FormData();
formData.append('image', avatarImage);
try {
const response = await axios({
method: 'PUT',
url: `${keys.SERVER_URL}/user/avatar/${isAuthenticated()._id}`,
data: formData,
headers: { 'Content-Type': 'multipart/form-data',}
});
localStorage.setItem(localStorageName, JSON.stringify({ avatar: response.data.user.avatar }));
} catch (error) {
console.log(error);
}
setReload(prevState => !prevState);
}
The thing is that the image is getting uploaded and is displayed correctly. The problem is that after I press on upload button the image is not shown instantly but I have to manually reload the page. Thats why I added that setReload. So that when I press on upload button the value of reload variable gets changed, that triggering a reload in useEffect.
The best practice is to use setState or something similar depending on your state management. localStorage does not trigger rerenders. React isn't designed that way.

Value of state variable is lost - React

I want to build a CRUD in React with Laravel and Firebase. Everything is perfect when I'm working with text, but I got trouble when I try to upload an image to Firebase Storage. I can save it but I can't get its URL.
I wrote 2 "console.log". In the first one the URL is there, but the second one (when I try to get the URL from the state variable) doesn't return anything.
handleSubmit = event =>{
event.preventDefault();
const {imagen} = this.state;
if(imagen!=null){
const uploadTask = storage.ref(`imagenes/${imagen.name}`).put(imagen);
uploadTask.on('state_changed',
(snapshot) => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({progress});
},
(error) => {
console.log(error);
},
() => {
storage.ref('imagenes').child(imagen.name).getDownloadURL().then(url => {
this.setState({url});
console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)
})
});
}
var direccion = null;
const form = event.target;
let data = new FormData(form);
data.append('url', this.state.url);
console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE)
If you want to check the entire file:
https://github.com/AndresVasquezPUCE/project/blob/master/pelicula
I'm not a professional, so please don't be rude :D
this.setState is asynchronous
If you want to get the updated state value, add a callback and access the new state there like
this.setState({ url: 'some url'}, () => {
conosle.log(this.state.url);
});
Data is loaded from Firebase asynchronously. By the time your console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE) the data hasn't been loaded from Firebase yet, and the then hasn't been called yet.
Any code that needs the data from Firebase needs to either be inside the then() callback (such as console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)) or be called from there (such as this.setState({url})).

Resources