Passing down async fetched data via Context API - reactjs

The data is not displaying in the child component Store. In the Storecomponent, the console.log in the useEffect() hook returns undefined. I suspect the reason being that the fetchAPI function in the parent component is only called after myContext.Provider is rendered, thus the value of myContext.Provider is undefined.
How can I pass the data(hook state) I fetched from the API in Stores(parent) down to Store(child) in this case?
export const myContext = createContext()
const Stores = () =>{
const [data, setData ] = useState([])
const fetchAPI = async() => {
var res = await fetch('https://fortnite-api.theapinetwork.com/store/get')
var result = await res.json()
var final= result.map(item => item)
setData(final)
}
useEffect(() =>{
fetchAPI().then(console.log(data))
}, [data])
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<Link to={`stores/${item.itemId}`}><li>{item.item.name}</li></Link>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
const Store = () => {
const specific = useContext(myContext)
useEffect(
() => {
console.log(specific)
}
)
return(
<>
{specific.map( item => {
return(
<div>
<h2> Description: {item.name}</h2>
</div>
)
})}
</>
)
}

The way you use fetch api isn't correct I think. fetch returns a promise, so you need to return the promise in your fetchAPI method if you want to uses .then().
.then() allow you to grab the promise and work with it, here is your code changed to work:
import React, { Component, useState, useEffect, createContext } from "react";
import { render } from "react-dom";
const myContext = createContext()
const App = () =>{
const [data, setData ] = useState([]);
const fetchAPI = () => {
// It return a promise
return fetch('https://fortnite-api.theapinetwork.com/store/get');
}
useEffect(() =>{
fetchAPI().then(data =>
// try to call .json() method. this method returns a promise
data.json().then(json => {
// if .json() succeed, then do your stuff
console.log(json);
setData(json.data.map(item => item));
})
)
// if .json() fails, promise is rejected with your error
.then(err => console.log('err', err));
}, []); // empty array to prevent looping (rule : do not update a state you are passing in this deps array)
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<li><pre>{JSON.stringify(item)}</pre></li>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
render(<App />, document.getElementById("root"));
Here is the repro on Stackblitz.

Related

How to render the sorted array of objects using UseMemo ReactHooks

I'm trying to render the sorted array of objects using ReactHooks i have used useMemo for the same and redux as well. Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
I have put the post.js below as well.
I'm trying to render the sorted array of objects using ReactHooks i have used useMemo for the same and redux as well. Could someone suggest me the best practies for it. Any suggestions on what am i doing wrong here?
Thanks
HomePage.js
import React, { useState, useEffect, useMemo } from "react";
import Post from "../../Components/Post/Post";
import "./HomePage.css";
import axios from "axios";
const HomePage = () => {
const [posts, setPosts] = useState("");
let config = { Authorization: "................" };
const url = ".........................";
useEffect(() => {
AllPosts();
}, []);
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
allPosts.sort((a, b) => a.title.localeCompare(b.title)), [posts];
});
return (
<div className="home">
<div className="select">
<select
name="slct"
id="slct"
onChange={(e) => newPostsByTitle(e.target.value)}
></select>
</div>
<Post className="Posts" posts={posts} key={posts.title} />
</div>
);
};
export default HomePage;
Post.js
import React from "react";
import "./Post.css";
import { Fragment } from "react";
const Post = (props) => {
const displayPosts = (props) => {
const { posts } = props;
if (posts.length > 0) {
return posts.map((post) => {
return (
<Fragment>
<div className="Post" key={post.title}>
<img
src={post.urlToImage}
alt="covid"
width="100%"
className="img"
/>
<h5 className="title"> {post.title}</h5>
<p className="author"> {post.author}</p>
<p className="description"> {post.description}</p>
</div>
</Fragment>
);
});
}
};
return <div className="Posts">{displayPosts(props)}</div>;
};
export default Post;
You have a incorrect understanding of what the axios call dos I think.
This is just a function that on trigger will download the data, but you need to store it somewhere (e.g. posts) and use these posts instead of the api call:
const [posts, setPosts] = useState([]); // Use an empty array as defualt so it does work without data before the call
...
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
setPosts(allPosts) ?? You need to save the posts somewhere, since allPosts is not accessible outside of this function. Sicne you already have a useState, save them there
console.log(response);
})
.catch((error) => console.error(`Error: ${error}`));
};
const newPostsByTitle = useMemo(() => {
return posts.sort((a, b) => a.title.localeCompare(b.title)), [posts]; // Using {} requeores the return keyword, if you omit the {} you dont need the return statement
}); // Now access the posts saved in state to sort them
Also the key in <Post className="Posts" posts={posts} key={posts.title} /> does not work, since posts is an array not an object. So remove it.

Getting map data using React from firestore

I am having trouble trying to figure out how to get map data from Firestore in reactjs. My code keeps erroring saying "Objects are not valid as a React". Can someone point me to an example or show me one with my database below?
import React, { useState, useEffect } from "react";
import { firestore } from "../../../FireBase/FireBase";
import CartItem from "./CartItem";
const CartPage = (props) => {
const [cart, setCart] = useState(null);
useEffect(() => {
const fetchCart = async () => {
const doc = await firestore
.collection("Users")
.doc("CfL5uszL3CTE1nIQTgDrKK5q4OV2")
.get();
const data = doc.data();
console.log("data " + data);
if (!data) {
// document didn't exist
console.log("hit null");
setCart(null)
} else {
console.log("hit");
setCart(data.cart);
}
console.log("cart " + cart);
}
fetchCart();
}, []);
if (!cart) {
// You can render a placeholder if you like during the load, or just return null to render nothing.
return null;
}
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart">
{cart.map(cartItem => (
<div key={cartItem.id}>{cartItem.name}</div>
))}
</div>
</div>
);
};
export default CartPage;
The error your getting is because you're returning a promise from your component (You've made it an async function, and async functions return promises). Promises and other arbitrary objects cannot be returned from rendering in react. You need to have a state variable for holding your data. On the first render, you'll have no data, and then you'll use a useEffect to fetch the data and update the state
Additionally, you have some mistakes with how you're trying to get the data and access it. You're calling .get("Cf...V2"), but .get doesn't take a parameter. If you want to specify which document to get, you use the .doc() function for that. .get() will then return a promise, so you need to await that before trying to access any properties on it. The data you get will be an object with all the properties on the right hand side of your screenshot, and you will need to pluck the cart property out of that.
In short, i recommend something like the following:
const CartPage = (props) => {
const [cart, setCart] = useState(null);
useEffect(() => {
const fetchCart = async () => {
const doc = await firestore
.collection("Users")
.doc("CfL5uszL3CTE1nIQTgDrKK5q4OV2")
.get();
const data = doc.data();
if (!data) {
// document didn't exist
setCart(null)
} else {
setCart(data.cart);
}
}
fetchCart();
}, []);
if (!cart) {
// You can render a placeholder if you like during the load, or just return null to render nothing.
return null;
}
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart">
{cart.map(cartItem => (
<div key={cartItem.id}>{cartItem.name}</div>
))}
</div>
</div>
);
};
I don't think so that you can create async component in this way. What you return in your component should be simple JSX code. If you want to do something asynchronously inside component you should wrap this inside useEffect hook.
const CartPage = (props) => {
const [ cart, setCart ] = useState(null)
useEffect(() => {
const inner = async () => {
const ref = await firestore
.collection("Users")
.get("CfL5uszL3CTE1nIQTgDrKK5q4OV2").cart;
setCart(
ref.map((item) => ({
id: item.id,
name: item.name
}))
);
};
inner();
}, []);
return (
<div className="cartpage">
<h1>cart</h1>
<div className="cart"></div>
</div>
);
};

How do use map function over array of objects in React

Having done the necessary to read the data using fetchAPI, I am having problems displaying the content because of the nature of the array.
import React, { useState, useEffect } from "react";
function Home() {
const [userData, setUserData] = useState([]);
async function getData() {
let response = await fetch("https://api.xxxxxxxx.io/something/students");
let data = await response.json();
return data;
}
//call getData function
getData().then((data) => console.log(data)); //
useEffect(() => {
getData()
.then((data) => {
setUserData(data);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div>
{Object.keys(userData).map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
);
}
export default Home;
When I checked the console, the data are displayed but it is only showing students with no other data displayed.
I have the data below.
Try the following changes:
const [userData, setUserData] = useState({ students: [] });
...
return (
<div>
{userData.students.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
);

How to fetch data from restAPI and from localStorage at a time?

What I am doing is to load data from my local storage which I have fetched from https://jsonplaceholder.typicode.com/users. I am trying to create a react application in which I have added multiple users as a friend just like facebook. my friend list is in the file called UserInfo.js which code I have given bellow. then I tried to show friends corresponding to their id by comparing with the id which I tried to find from api call so that this can show me the matching users.
You can find my project here: https://codesandbox.io/s/elastic-dream-mp3is?file=/src/App.js
import React, { useState } from "react";
import "./UserInfo.css";
import { useEffect } from "react";
import fakedata from "../../fakedata";
import { getDatabaseCart } from "../../utilities/databaseManager";
const UserInfo = () => {
// // using fakedata
// const [users, setUsers] = useState(fakedata);
// // declaring state while calling api
const [users, setUsers] = useState([]);
// declaring state while loading data from local storage
const [friends, setFriends] = useState(users);
// to load data from Api call
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
// to load data from local storage
useEffect(() => {
const savedFriends = getDatabaseCart();
const friendsId = Object.keys(savedFriends);
const countFriends = friendsId.map((key) => {
const friend = friends.find((fd) => fd.id == key);
// console.log(friend);
return friend;
});
setFriends(countFriends);
}, [friends]);
// console.log(friends);
// console.log(users);
return (
<div className="userInfo-container">
<div className="userInfo">
{friends.map((friend) => (
<div>
<h4>Name: {friend.name}</h4>
</div>
))}
</div>
</div>
);
};
export default UserInfo;
I have created fakedata collecting data from jsonplaceholder and tested according to above method and it worked perfectly. but when I tried to load data API call, I got the following error:
the first error indicates that it can not read property of name which I tried to return from local Storage after matching the id with api call.
2.second error denotes which I can't understand in my case. I have tried abortCall to handle this error. the error gone but my problem still exits. what can I do now????
With hooks:
import React, { useState } from "react";
//import "./UserInfo.css";
import { useEffect } from "react";
import fakedata from "../../fakedata";
import { getDatabaseCart } from "../../utilities/databaseManager";
const UserInfo = () => {
// // using fakedata
// const [users, setUsers] = useState(fakedata);
// // declaring state while calling api
const [users, setUsers] = useState([]);
// declaring state while loading data from local storage
const [friends, setFriends] = useState([]);
// to load data from Api call
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
// to load data from local storage
useEffect(() => {
const savedFriends = getDatabaseCart();
const friendsId = Object.keys(savedFriends);
setFriends(friendsId);
}, []);
// console.log(friends);
// console.log(users);
return (
<div className="userInfo-container">
<div className="userInfo">
{users.length > 0 && friends.map((friendId) => {
const friend = users.find(user => user.id === parseInt(friendId));
return (
<div>
<h4>Name: {friend && friend.name}</h4>
</div>
)})}
</div>
</div>
);
};
export default UserInfo;
In the render, Friends return [{0:undefined}]. You are not setting friends correctly.
I make a Class version of your UserInfo component. It works like this.
import React from "react";
import { getDatabaseCart } from "../../utilities/databaseManager";
class UserInfo extends React.Component {
state = {
users: [],
friends: []
}
componentDidMount = () => {
this.getDataFromServer();
this.getDateFromLocalStorage();
}
getDataFromServer = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => this.setState({users:data}));
}
getDateFromLocalStorage = () => {
const savedFriends = getDatabaseCart();
const friendsId = Object.keys(savedFriends);
//console.log(friendsId)
this.setState({friends:friendsId})
}
render() {
const {users, friends} = this.state;
// console.log('users',users, friends);
// console.log('friends', friends);
return (
<div className="userInfo-container">
<div className="userInfo">
{users.length > 0 && friends.map((friendId) => {
const friend = users.find(user => user.id === parseInt(friendId));
return (
<div>
<h4>Name: {friend && friend.name}</h4>
</div>
)})}
</div>
</div>
);
}
}
export default UserInfo;
Note you have a promise for the 'fetch' so at first render and until users are fetched user = [] empty array. To avoid errors it's good to check the length of users before try to map friends.
You can remove the friend check here because if friends array is empty, there is nothing to map.
return (
<div>
<h4>Name: {friend.name}</h4>
</div>
)
Now, it should be a list with ul and li.

NextJS how to fetch data after click event?

I have problem with load data to component after click on button.
I use getInitialProps to first load data on page.
How to load new data and past them to {data} after click?
export default function Users({ data }) {
const fetchData = async () => {
const req = await fetch("https://randomuser.me/api/?gender=male&results=100");
const data = await req.json();
return { data: data.results };
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch(
"https://randomuser.me/api/?gender=female&results=10"
);
const data = await req.json();
return { data: data.results };
};
Thank a lot for help!
Use useState with the default value being the data you initially retrieved via getInitialProps:
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
return setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map((user) => {
return (
<div>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
Sidenote: Times have changed and it would seem that user1665355 is indeed correct:
Recommended: getStaticProps or getServerSideProps
If you're using Next.js 9.3 or newer, we recommend that you use
getStaticProps or getServerSideProps instead of getInitialProps.
These new data fetching methods allow you to have a granular choice
between static generation and server-side rendering.
import { useState } from 'React';
export default function Users({ initialData }) {
const [data, setData] = useState(initialData);
const fetchData = async () => {
const req = await fetch('https://randomuser.me/api/?gender=male&results=100');
const newData = await req.json();
setData(newData.results);
};
const handleClick = (event) => {
event.preventDefault();
fetchData();
};
return (
<Layout>
<button onClick={handleClick}>FETCH DATA</button>
{data.map(user => {
return (
<div key={user.login.uuid}>
{user.email}
<img src={user.picture.medium} alt="" />
</div>
);
})}
</Layout>
);
}
Users.getInitialProps = async () => {
const req = await fetch('https://randomuser.me/api/?gender=female&results=10');
const data = await req.json();
return { initialData: data.results };
};
I would like to list my notes about George's code. At least, it should pay attention to them.
First of all, it should attach any key to a div element otherwise a warning will have appeared in the browser console. Here is an article about using keys: https://reactjs.org/docs/lists-and-keys.html#keys
As well, the keyword return can be removed from the fetchData function that doesn't return a response.
It is recommended to use getStaticProps or getServerSideProps now. https://nextjs.org/docs/api-reference/data-fetching/getInitialProps

Resources