props getting passed irrationally - reactjs

I am using the following code for fetching and passing data from firestore.
there are four data in my firestore collection. I am able to fetch all four of them, (i checked this on console), but in NewRecipe component only the first data is getting passed as props. not sure why the other three data not getting passed as props.
this is component where i am fetching data :
const Home = () => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setIsPending(true);
projectFirestore
.collection("recipes")
.get()
.then((snapshot) => {
if (snapshot.empty) {
setError("No recipes");
setIsPending(false);
} else {
let result = [];
snapshot.docs.forEach((doc) => {
result.push({ id: doc.id, ...doc.data() });
setData(result);
setIsPending(false);
});
}
})
.catch((e) => {
setError(e);
});
}, []);
console.log("data", data);
return (
<div>
{error && <div className="errors">{error}</div>}
{isPending && <div className="Loading">Loading.......</div>}
{data && <NewRecipe data={data} />}
</div>
);
};
export default Home;
this is NewRecipe component where I am receiving props
const NewRecipe = ({data}) => {
console.log("data new recipe", data)
return (
<div className='recipe_list'>
{data.map((item)=>{
return (
<div key={item.id} className='card'>
<h3>{item.title}</h3>
<p>it takes around {item.cookingTime}</p>
<p>{item.method.substring(0,100)}</p>
<Link to={`/recipes/ ${item.id}`}className='btn'> Read more</Link>
</div>
)})}
</div>
)
}

Try to place setPending(false) outside (just after) the forEach block.

Related

Render fetched API json object in react component Typescript

i have my json received from api call and is saved in the state "data"
i want to show a loading screen while api is being fetched like i have a state for that too "Loading"
Loading ? Then render data on elements : Loading..
const App = () => {
const [data, setData] = useState([]);
const [Loading, setLoading] = useState(false);
useEffect(() => {
Fetchapi();
}, []);
const Fetchapi = async () => {
try {
await axios.get("http://localhost:8081/api").then((response) => {
const allData = response.data;
setData(allData);
});
setLoading(true);
} catch (e) {
console.log(e);
}
};
return (
<div>
i need my json object rendered here i tried map method on data and i am
getting errors and i have my json2ts interfaces imported in this
</div>
);
};
export default App;
I would camelCase your values/functions and move your fetchApi into the effect itself, as currently its a dependency.
Put setLoading(true) above your fetch request as currently it's not activating until the fetch goes through.
Then below it put setLoading(false), and also inside of your catch.
In your return statement you can now add something like this:
<div>
{loading ? "Loading..." : JSON.stringify(data)}
</div>
Edit
Example for the commented requests.
import { Clan } from "../clan.jsx"
// App
<div>
{loading ? "Loading..." : <Clan data={data}/>}
</div>
// New file clan.jsx
export const Clan = (props) => {
return (
<div>
<h1>{props.data.clan.name}</h1>
</div>
);
}
try this
interface ResponseData {
id: string
// other data ...
}
const App = () => {
const [data, setData] = useState<ResponseData | null>(null)
const [Loading, setLoading] = useState(true)
useEffect(() => {
Fetchapi()
}, [])
const Fetchapi = async () => {
try {
setLoading(true) // USE BEFORE FETCH
await axios.get("http://localhost:8081/api").then(response => {
setLoading(false) // SET LOADING FALSE AFTER GETTING DATA
const allData: ResponseData = response.data
setData(allData)
})
} catch (e) {
setLoading(false) // ALSO SET LOADING FALSE IF ERROR
console.log(e)
}
}
if (Loading) return <p>Loading...</p>
if (data?.length)
return (
<div>
{data.map(d => (
<div key={d.id}>{d.id}</div>
))}
</div>
)
return <div>no data found</div>
}
export default App

How do I fetch the data from the API? I always get Undefined

I'm practising React with a small project where I want to display some Nba players but I don't get any data when trying to map an object.
I'm using this Api: http://data.nba.net/prod/v1/2022/players.json
Here is the code:
import React, { useEffect, useState } from "react";
const Players = () => {
const url = "http://data.nba.net/prod/v1/2022/players.json";
const [players, setPlayers] = useState([]);
useEffect(() => {
getPlayers();
}, []);
const getPlayers = async () => {
const api = await fetch(url);
const data = await api.json();
//wrapping a object into a array
setPlayers([data].flat());
};
return (
<div>
<h3>Sacramento player info</h3>
<ul>
{players.map((player) => (
<li key={player.league.sacramento.id}>
{player.league.sacramento.firstName}{" "}
{player.league.sacramento.lastName}{" "}
</li>
))}
</ul>
</div>
);
};
export default Players;
I recreated your code on codesandbox and it works just fine. I use other approach on getting data thru fetch and changed http:// to https://
const Players = () => {
const [data, setData] = useState(null);
function getAPIData() {
fetch("https://data.nba.net/prod/v1/2022/players.json")
.then((response) => {
if (response.ok) {
return response.json();
}
throw new Error("ERROR (response not ok)");
})
.then((data) => {
setData(data);
})
.catch((response) => {
console.log("error");
});
}
useEffect(() => getAPIData(), []);
return (
data && (
<div>
<h3>Sacramento player info</h3>
<ol>
{data.league.sacramento.map((player) => (
<li key={player.personId}>
{player.firstName} {player.lastName}
</li>
))}
</ol>
</div>
)
);
};
working code: https://codesandbox.io/s/players-info-51gf1w

useEffect infinite loop when fetching from the api

I'm trying to fetch some data from the API, but doesn't matter which dependencies I use, useEffect still keeps making an infinite loop, is there something wrong in the code or why it keeps doing that?
function HomePage() {
const [Carousels, setCarousels] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const getCarousels = async () => {
setLoading(true);
const genres = ["BestRated", "Newest"];
for (const genre of genres) {
try {
const res = await axios.get(`http://localhost:4000/api/carousels/` + genre);
console.log(res.data);
setCarousels([...Carousels, res.data]);
} catch (err) {
console.log(err);
}
}
setLoading(false);
}
getCarousels();
}, [Carousels]);
return (
<div className='Home'>
<NavBar />
<HeroCard />
{!loading && Carousels.map((carousel) => (
<Carousel key={carousel._id} carousel={carousel} />
))}
<Footer />
</div>
);
}
Use effect called when Carousels changed and Carousels changed inside useEffect.
Use set state with callback
function HomePage() {
const [Carousels, setCarousels] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const getCarousels = async () => {
setLoading(true);
const genres = ["BestRated", "Newest"];
for (const genre of genres) {
try {
const res = await axios.get(`http://localhost:4000/api/carousels/` + genre);
console.log(res.data);
setCarousels(carouselsPrev => [...carouselsPrev, res.data]);
} catch (err) {
console.log(err);
}
}
setLoading(false);
}
getCarousels();
}, []);
return (
<div className='Home'>
<NavBar />
<HeroCard />
{!loading && Carousels.map((carousel) => (
<Carousel key={carousel._id} carousel={carousel} />
))}
<Footer />
</div>
);
}
useEffect in your code updates Carousels each run. It sees the updated value and runs again. You could fix it various ways, the easiest would be to add [fetched, setFetched] = useState(false); and use it in your useEffect to check before fetching from the API

Displaying fetched data from API

function App() {
const [todos, setTodos] = useState(null);
useEffect(() => {
const GetTodos = async () => {
try {
const { data } = await axios.get("api/orders");
console.log(data);
setTodos(data);
console.log(todos);
} catch (err) {
console.log(err);
}
};
GetTodos();
}, []);
return (
<div className="App">
<h1>hello</h1>
{todos?.map((todo) => (
<p key={todo.ID}>{todo.ID}</p>
))}
</div>
);
}
How can I make the data I got from the API display on the page, I can see that it works when I log it to the console but it doesn't show up on the page
Okay the problem is your data returns an Array of Arrays.
Because your data has just one array nested inside, we can just use it as the data hook, something like this:
setTodos(data[0]);
to understand, here is an example
You could do as below, calling setTodos(data[0]) in your try-catch, as your API seems to be returning an array with the data you want at position 0.
function App() {
const [todos, setTodos] = useState(null);
useEffect(() => {
const GetTodos = async () => {
try {
const { data } = await axios.get("api/orders");
console.log(data);
setTodos(data[0]);
} catch (err) {
console.log(err);
}
};
GetTodos();
}, []);
return (
<div className="App">
<h1>hello</h1>
{todos && todos.map((todo) => (
<p key={todo.ID}>{todo.ID}</p>
))}
</div>
);
}

Too many re-renders for component

I am trying to call a component that shows the details of a notification when the notification is clicked. However, I kept on getting an error of too many re-renders.
This is my Notifications code
This component calls the database to get the list of notifications and then sets the first notification as the default notification clicked.
const Notification = (hospital) => {
const [users, setUsers] = useState([]);
const [search, setSearch] = useState(null);
const [status, setStatus] = useState(null);
const [notifDetails, setNotification] = useState();
useEffect(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
}, []);
return(
<div className="hospital-notif-container">
{filteredList(users, status, search).map((details, index) => {
for (var i = 0; i < details.receiver.length; i++) {
if (
(details.receiver[i].id === hospital.PK ||
details.receiver[i].id === "others") &&
details.sender.id !== hospital.PK
) {
return (
<div
className="hospital-notif-row"
key={index}
onClick={() => setNotification(details)}
>
<div className="hospital-notif-row">
{details.name}
</div>
</div>
);
}
}
return null;
})}
</div>
<NotificationDetails details={notifDetails} />
);
}
For NotificationDetails:
This function is triggered when a notification is clicked from Notifications. The error is said to be coming from this component.
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (Object.keys(details).length != 0) {
setLoading(false);
}
}, [details]);
if (!loading) {
return (
<>
<div className="hospital-details-container">
<h2>{details.sender.name}</h2>
</div>
</>
);
} else {return (<div>Loading</div>);}
};
What should I do to limit the re-render? Should I change the second argument of the useEffects call? Or am I missing something in my component?
I tried calling console.log from NotificationDetails and it shows that it is infinitely rendering with the data I set in axios which is result[0]. How is this happening?
Your problem should be in NotificationDetails rendering. You should write something like:
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (details.length != 0) {
setLoading(false);
}
}, [details]);
return (
<div>
{loading &&
<div className="hospital-details-container">
<div className="hospital-details-header">
<h2>{details.sender.name}</h2>
</div>
</div>
}
{!loading &&
<div>
<ReactBootStrap.Spinner animation="border" />
</div>
}
</div>
);
}
With return outside the condition statement.
EDIT
Now I noted that you have an async useEffect that is an antipattern. You should modify your useEffect in this way:
useEffect(() => {
(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
})()
}, []);

Resources