How can I manage promise and map objects? - reactjs

I am working on a project in which data in my database is something like this:
I'm not getting what wrong am I doing! here is my
FetchList.js
import { useState } from "react";
const FetchList = async()=>{
const[data, setData] = useState()
const response = await fetch('https://__firebase__url__');
if (!response.ok) {
throw new Error("Something went wrong");
}
let responseData = await response.json();
const loadedHospitals = [];
for (const key in responseData){
loadedHospitals.push({
key: responseData[key].key,
Name: responseData[key].Name,
Email: responseData[key].Email,
Contact_no: responseData[key].Contact_no,
img: responseData[key].img,
});
}
setData(loadedHospitals);
const hospitals = data.map(hospital => {
console.log(hospital);
});
return data;
}
export default FetchList;
I want to pass the entire result in an array to ListHospital.js and then map it with Card component.
import Card from "../UI/Card";
import FetchList from "./FetchList";
import { useState, useEffect } from "react";
const ListHospitals = () => {
const [data, setData] = useState();
useEffect(() => {
FetchList().then(data => setData(data));
}, []);
return data.map((item)=><Card>{item}</Card>);
}
export default ListHospitals;
Error:
TypeError: Cannot read properties of undefined (reading 'map')

I think you need to write const [data, setData] = useState(); as const [data, setData] = useState([]); first. as fetch list is an async function so it will take some time to fetch your data hence it is giving you an error. You can only use map on arrays and during the initial render the data variable is undefined.

Fetching query at the higher level like App.js solved my problem.
Like :-
function App() {
const [ data, setData ] = useState([]);
const fetching = async () => {
const response = await fetch("_____url___");
const responseData = await response.json();
let loadedItem = [];
for (const key in responseData) {
loadedItem.push(responseData[key]);
}
console.log(loadedItem);
}
fetching();
return (
<>
<h1>Hey</h1>
</>
);
}
export default App;

Related

How make useEffect work with chaining async/wait map?

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) => {

Problem with fetching data in React, Assignment to constant variable

I just started to learn React and was trying to fetch some random data. i created a useState and have two values : const [name, setName] = useState([]);
when i try to do name : response.json();
I get an error that assignment to a constant variable, I'm following a tutorial which is doing the same.
surprisingly when I create a constant variable with a different name, it works. I only can't assign the name = await response.json();
Thank you
import React, { useEffect, useState } from "react";
import { ReactDOM } from "react";
const FetchData = () =>{
const [name, setName] = useState([]);
const fetchNames = async () =>{
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
name = await response.json();
console.log(name);
setName(name);
} catch (error) {
console.log(error);
}
}
useEffect(()=>{
fetchNames();
},[])
return(
<div>
</div>
);
}
export default FetchData;
Check my example, as I understand you want to use name inside the try as variable not from state so you should add const. I also mapped the response in your render which you can see the results. Here also codesandbox example https://codesandbox.io/embed/friendly-ishizaka-57i66u?fontsize=14&hidenavigation=1&theme=dark
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [name, setName] = useState([]);
const fetchNames = async () => {
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
constname = await response.json();
console.log(name);
setName(name?.results);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchNames();
}, []);
return (
<div>
{name.map((el) => (
<>
<p>{el.email}</p>
<p>{el.phone}</p>
</>
))}
</div>
);
}
there are 2 things that might help with this confusion
any variable created using const will forever hold the value it had when it was declared, so for eg if we have const x = "😀", we later can't do something like x = "something else"
in the snippet you shared name is a "state variable". state variables should NEVER be updated directly, state updates must happen through setState function only(i.e. setName in this case)
this is the part in official docs that talks about it https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly , https://reactjs.org/docs/hooks-state.html#updating-state

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>
);
};

Getting 'undefined' at component level

I am getting undefined when I run the code below. However, If I console.log the results within the hook, I get all the data
hook (works fine, fetches the data)
import { useState, useEffect } from 'react';
import axios from 'axios';
export const GetOrders = () => {
const [data, setData] = useState();
useEffect(() => {
axios.get('/allorders').then(res => {
setData(res.data);
});
}, []);
console.log(data);
return { data };
};
component (returns undefined when I log the data)
import React from 'react';
import { GetOrders } from '../hooks/orders';
export const AllOrders = () => {
const { data } = GetOrders();
console.log(data);
return (
<ul>
{data.forEach(order => (
<li>{order.status}</li>
))}
</ul>
);
};
Your code looks good. Just initialize data with [] value so it will not break when you will loop over values since undefined.map() will fail
const [data, setData] = useState([]);

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