How make useEffect work with chaining async/wait map? - reactjs

I'm making an API call and then afterwards there is a chaining async/await.
When the page loads, the code is not executing the chaining part, I'm getting just the arrayDrivers.
How can I make the useEffect perform the chaining async/wait when the page loads?
import { useState, useEffect } from "react";
import DriversList from "../components/DriversList";
import axios from "axios";
const GetDrivers = () => {
const [drivers, setDrivers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchDriver = async () => {
const res1 = await axios.get("http://ergast.com/api/f1/2022/drivers.json?limit=25");
const arrayDrivers = res1.data.MRData.DriverTable.Drivers;
setDrivers(arrayDrivers);
await Promise.all(
drivers.map(async (driver) => {
const searchTitle = `/api.php?action=query&generator=search&format=json&gsrsearch=${driver.givenName}_${driver.familyName}&gsrlimit=1&prop=info`;
const res2 = await axios.get(searchTitle);
const driverTitle = Object.values(res2.data.query.pages)[0].title;
const linkPhoto = `/api.php?action=query&titles=${driverTitle}&prop=pageimages&format=json&pithumbsize=400`;
const res3 = await axios.get(linkPhoto);
const thumbSource = Object.values(res3.data.query.pages)[0].thumbnail.source;
driver.photo = thumbSource;
console.log(driver);
}, setIsLoading(false))
);
};
fetchDriver();
// eslint-disable-next-line
}, []);
return <>{isLoading ? <p>Loading...</p> : <DriversList drivers={drivers} />}</>;
};
export default GetDrivers;

You're updating state before you've finished modifying your data:
setDrivers(arrayDrivers);
await Promise.all(
//...
);
Modify the data, then use that data to update state:
await Promise.all(
//...
);
setDrivers(arrayDrivers);
Additionally, you're not modifying the actual data. You're trying to modify the empty array from state:
drivers.map(async (driver) => {
The data you got from the server is in arrayDrivers:
arrayDrivers.map(async (driver) => {

Related

I want to fetch weather data based on the users geolocation. Thought I had this figured out but I don't

I'm sure this has to do with the timing geolocation and axios fetching the data. With useEffect
it will give the wrong location. If I call the function without useEffect I will get the correct location but this creates an infinite loop. I've also tried using a condition in the useEffect.
and have tried using setTimeout;
How do I make sure navigator.geolocation has the lat and long before fetching the data?
import React from 'react';
import { useState, useEffect } from 'react';
import './css/weather.css'
import axios from 'axios';
const Weather = () => {
const [lat, setLat] = useState(0);
const [long, setLong] = useState(0);
const [temp, setTemp] = useState(0);
const [wind, setWind] = useState('');
const [windDir, setWindDir] = useState('');
const [gust, setGust] = useState('');
const [precip, setPrecip] = useState(0);
const [icon, setIcon] = useState('');
const [pic, setPic] = useState('');
const [city, setCity] = useState('');
const getWeather = async () => {
try {
await navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
})
const response = await axios.get(`http://api.weatherapi.com/v1/current.json?key=6e6263afb84f44279f731543222510&q=${lat},${long}&aqi=no`);
console.log(response.data)
setCity(response.data.location.name)
setTemp(response.data.current.temp_f);
setWind(response.data.current.wind_mph);
setWindDir(response.data.current.wind_dir);
setGust(response.data.current.gust_mph);
setPrecip(response.data.current.precip_in);
setPic(response.data.current.condition.icon.slice(-7))
if(response.data.current.condition.icon.includes('day')){
setIcon(<img src={`/assets/weather/64x64/day/${pic}`}></img>
)
} else {
setIcon(<img src={`/assets/weather/64x64/night/${pic}`}></img> )
}
} catch (err) {
console.error(err)
}
}
useEffect(() =>{
getWeather();
},[])
return (
<div className='weather-container'>
<p>{icon}</p>
<h2>{city}</h2>
<p>{`Current Temp: ${Math.round(temp)} °F`}</p>
<p>{`Wind: ${windDir} ${Math.round(wind)} mph Gusts: ${Math.round(gust)} mph`}</p>
<p>{`Precip: ${Math.round(precip)}in`}</p>
</div>
)
}
export default Weather;
I think I have found the solution. Putting lat in useEffect as an dependency seems to have solved the problem.
useEffect(() => {
getWeather();
}, [lat]);

How to automatically refresh getstream.io FlatFeed after a new post using reactjs?

I would like to understand how can I auto-update the feed after submitting the form through the StatusUpdateForm component. At the moment I have to refresh the page to see the changes.
In general, my task is to differentiate feeds based on the user's location, I requested extended permissions from support so that different users can post to one feed, and therefore I use the modified doFeedRequest parameters of the FlatFeed component to show the feed without being tied to the current user and it works.
I do not use notification, I want the posted messages to appear immediately in the feed.
If I wrote my own custom feed (FeedCustom) component to display data, it would work fine, but how do I make it work with FlatFeed of getstream.io? Any help would be greatly appreciated.
import React, { useEffect, useState } from 'react';
import { StreamApp, FlatFeed, StatusUpdateForm } from 'react-activity-feed';
import 'react-activity-feed/dist/index.css';
// import FeedCustom from './FeedCustom';
const STREAM_API_KEY = 'XXXXXXXXXXXXXXXX';
const STREAM_APP_ID = 'XXXXX';
const App = () => {
const [userToken, setUserToken] = useState(null);
const [loading, setLoading] = useState(true);
const [locationId, setLocationId] = useState(null);
const [data, setData] = useState([]);
const callApi = async () => {
const response = await fetch('https://localhost:8080/user-token')
const userResponse = await response.json();
return userResponse;
};
useEffect(() => {
callApi()
.then(response => {
const resp = JSON.parse(response.body);
setLoading(false);
setUserToken(resp.userToken);
setLocationId(resp.locationId);
})
.catch(e => alert(e));
}, []);
const customDoFeedRequest = (client, feedGroup = 'timeline', userId = locationId, options) => {
const feed = client.feed(feedGroup, userId);
const feedPromise = feed.get(options);
feedPromise.then((res) => {
setData((data) => res.results);
});
return feedPromise;
}
return loading ? (
<div>.... Loading ....</div>
) : (
<StreamApp
apiKey={STREAM_API_KEY}
appId={STREAM_APP_ID}
token={userToken}
>
{/* <FeedCustom dataFeed={ data } /> */}
<FlatFeed doFeedRequest={customDoFeedRequest} />
<StatusUpdateForm
userId={locationId}
feedGroup={'timeline'}
onSuccess={(post) => setData((data) => [...data, post])}
/>
</StreamApp>
)
};
export default App;
My backend https://localhost:8080/user-token returns an object kind of:
{
userToken: 'XXXXXXX'
locationId: 'XXXXXXX'
}

custom hooks return value doesn't change in Component

My custom hook fetches data asynchronously. When it is used in a component, returned value doesn't get updated. It keeps showing default value. Does anybody know what is going on? Thank you!
import React, {useState, useEffect} from 'react'
import { getDoc, getDocs, Query, DocumentReference, deleteDoc} from 'firebase/firestore'
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined)
const [isLoading, setIsLoading] = useState<boolean>(true)
const update = async () => {
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
const data = docSnap.data()
setValue(data)
}
setIsLoading(false)
}
useEffect(() => {
update()
}, [])
console.log(value, isLoading) // it can shows correct data after fetching
return {value, isLoading}
}
import { useParams } from 'react-router-dom'
const MyComponent = () => {
const {userId} = useParams()
const docRef = doc(db, 'users', userId!)
const {value, isLoading} = useFirestoreDocument(docRef)
console.log(value, isLoading) // keeps showing {undefined, true}.
return (
<div>
...
</div>
)
}
It looks like youe hook is only being executed once upon rendering, because it is missing the docRef as a dependency:
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined)
const [isLoading, setIsLoading] = useState<boolean>(true)
useEffect(() => {
const update = async () => {
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
const data = docSnap.data()
setValue(data)
}
setIsLoading(false)
}
update()
}, [docRef])
console.log(value, isLoading) // it can shows correct data after fetching
return {value, isLoading}
}
In addition: put your update function definition inside the useEffect hook, if you do not need it anywhere else. Your linter will complaing about the exhaustive-deps rule otherwise.
The useEffect hook is missing a dependency on the docRef:
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
const update = async () => {
setIsLoading(true);
try {
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data();
setValue(data);
}
} catch(error) {
// handle any errors, log, etc...
}
setIsLoading(false);
};
update();
}, [docRef]);
return { value, isLoading };
};
The render looping issue is because docRef is redeclared each render cycle in MyComponent. You should memoize this value so a stable reference is passed to the useFirestoreDocument hook.
const MyComponent = () => {
const {userId} = useParams();
const docRef = useMemo(() => doc(db, 'users', userId!), [userId]);
const {value, isLoading} = useFirestoreDocument(docRef);
console.log(value, isLoading);
return (
<div>
...
</div>
);
};

React Hook useEffect has a missing dependency: 'fetchUser'. useEffect problem?

I'm new to react and I'm learning how to use useEffect. I encountered this warning in my react app. I tried out some solutions on SO but the warning still remains. Both fetchUser and fetchPosts trigger this warning. Can anyone enlighten me what is the problem and what does the warning mean?
App.js
useEffect(() => {
setLoading(true)
const getUser = async () => {
const userFromServer = await fetchUser()
if (userFromServer) {
setUser(userFromServer)
setLoading(false)
} else {
console.log("error")
}
}
getUser()
}, [userId])
useEffect(() => {
const getPosts = async () => {
const postsFromServer = await fetchPosts()
setPosts(postsFromServer)
}
getPosts()
}, [userId])
useEffect(() => {
const getUserList = async () => {
const userListFromServer = await fetchUserList()
setUserList(userListFromServer)
}
getUserList()
}, [])
// Fetch user
const fetchUser = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
const data = await res.json()
return data
}
// Fetch posts
const fetchPosts = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
const data = await res.json()
return data
}
// Fetch list of users
const fetchUserList = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/')
const data = await res.json()
return data
}
If you are using any function or state which has been declared outside the useEffect then you need to pass it in the dependency array like this:
const someFunctionA = () => {
....
}
const someFunctionB = () => {
....
}
useEffect(() => {
....
}, [someFunctionA, someFunctionB])
You can read more about it here in case you want to know how it will be rendered: React useEffect - passing a function in the dependency array

Multiple fetch data axios with React Hooks

I would like to get global information from Github user and his repos(and get pinned repos will be awesome). I try to make it with async await but It's is correct? I've got 4 times reRender (4 times console log). It is possible to wait all component to reRender when all data is fetched?
function App() {
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(`https://api.github.com/users/${username}`);
const respRepos = await axios(`https://api.github.com/users/${username}/repos`);
setData(respGlobal.data);
setRepos(respRepos.data);
};
fetchData()
}, []);
if (data) {
console.log(data, repos);
}
return (<h1>Hello</h1>)
}
Multiple state updates are batched but but only if it occurs from within event handlers synchronously and not setTimeouts or async-await wrapped methods.
This behavior is similar to classes and since in your case its performing two state update cycles due to two state update calls happening
So Initially you have an initial render and then you have two state updates which is why component renders three times.
Since the two states in your case are related, you can create an object and update them together like this:
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
Working demo
Figured I'd take a stab at it because the above answer is nice, however, I like cleanliness.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Test = () => {
const [data, setData] = useState([])
useEffect(() => {
(async () => {
const data1 = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
const data2 = await axios.get('https://jsonplaceholder.typicode.com/todos/2')
setData({data1, data2})
})()
}, [])
return JSON.stringify(data)
}
export default Test
Using a self invoking function takes out the extra step of calling the function in useEffect which can sometimes throw Promise errors in IDEs like WebStorm and PHPStorm.
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
he made some mistake here:
setGitData({ data: respGlobal.data, repos: respGlobal.data(respRepos.data //it should be respRepos.data});
For other researchers (Live demo):
import React, { useEffect, useState } from "react";
import { CPromise, CanceledError } from "c-promise2";
import cpAxios from "cp-axios";
function MyComponent(props) {
const [error, setError] = useState("");
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
console.log("mount");
const promise = CPromise.from(function* () {
try {
console.log("fetch");
const [respGlobal, respRepos] = [
yield cpAxios(`https://api.github.com/users/${props.username}`),
yield cpAxios(`https://api.github.com/users/${props.username}/repos`)
];
setData(respGlobal.data);
setRepos(respRepos.data);
} catch (err) {
console.warn(err);
CanceledError.rethrow(err); //passthrough
// handle other errors than CanceledError
setError(err + "");
}
}, []);
return () => {
console.log("unmount");
promise.cancel();
};
}, [props.username]);
return (
<div>
{error ? (
<span>{error}</span>
) : (
<ul>
<li>{JSON.stringify(data)}</li>
<li>{JSON.stringify(repos)}</li>
</ul>
)}
</div>
);
}

Resources