How to pass an async object to a props in react? - javascript

I'm practicing react and how to incorporate react to firebase/firestore. Now I want to make something like a CMS using firestore database. I have successfully fetch data from the database and confirmed it by console log. But the problem is whenever I tried to pass the object to be used as my props it throws me an error. But if I add the code after the page is fully loaded, the data is successfully loaded and throws me an error after I refresh the page.
I think it has something to do with asynchronous request but I don't know how to pull it off.
const app = initializeApp(firebaseConfig)
const db = getFirestore(app);
const [data, setData] = useState({});
const fetchData =async () => {
const docRef = doc(db, "data", "RSVp8ljO95Dpwa0oSs0G");
const docSnap = await getDoc(docRef);
const dataTest = docSnap.data();
await setData(dataTest);
console.log("Document data:", dataTest);
}
useEffect(() => {
fetchData();
}, [])
return (
<div style={{overflow: 'hidden'}}>
<NavBar />
<div style={{width: '100%', height:'81vh', padding: '5%', overflow:'scroll'}}>
<Container >
<div className="d-flex flex-wrap justify-content-center">
<TrainingSchedule
**date={data.event1.date} //I want to pass the object here but throws me an error**
month='SEPT'
eventTitle='Lets Get to know each other'
eventDescription='Have a drink with our finest coach and enjoy the summer'
time='1pm'
backgroundColor= 'CadetBlue'
/>
......

Hi #Juls: Welcome to StackOverflow.
In your example, you're trying to access properties on data that don't exist in the first render (before the effect hook runs). That's why you're getting the error. Instead, check to make sure the object and properties that you need exist before trying to access them:
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.15.7/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useEffect, useState} = React;
async function getDataFromAPI () {
await new Promise(res => setTimeout(res, 1000));
return { event1: { date: '2021-09-18' } };
}
function Example () {
// Leave the state value undefined instead of initializing it as an empty object
const [data, setData] = useState();
useEffect(() => {
const fetchData = async () => {
const fetchedData = await getDataFromAPI();
setData(fetchedData);
};
fetchData();
}, []);
return (
<div>
{
// Check if the data exists before trying to access its properties:
data
? (<div>{data.event1.date}</div>)
: (<div>Data is being fetched...</div>)
}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>

please provide us with the error so that we can fully understand the problem.
but for now, try this :
date={data.event1.date || null}
this should cause the date to become null if the data is not ready, then get updated in the next render which is caused by setState.

Related

Why does my component fail to construct Url?

I'm fairly new to React development and hope someone can help me with this problem. I'm coding along with a YouTube video https://www.youtube.com/watch?v=XtMThy8QKqU&t=10138s (2:55:00 shows what it is supposed to do)and for some reason I can't find the mistake I'm making. When I test my app on localhost the window in which the trailer is supposed to play is only displayed when I click certain movie covers but not when I click on others. my other problem is that it will never actually play a trailer. The console displays the error you can hopefully see here [1]: https://i.stack.imgur.com/vC6Sh.jpg
import movieTrailer from "movie-trailer";
import React, { useEffect, useState } from "react";
import YouTube from "react-youtube";
import axios from "./axios";
import "./Row.css"
const base_url = "https://image.tmdb.org/t/p/original/";
function Row({ title, fetchUrl, isLargeRow }) {
const [movies, setMovies] = useState([]);
const [trailerUrl, setTrailerUrl] = useState("");
//A snippet of code which runs based on a specific condition or variable
useEffect(() => {
// if brackets are blank [] it means run once when row loads, and don't run again
async function fetchData() {
const request = await axios.get(fetchUrl);
// console.log(request.data.results);
setMovies(request.data.results)
return request;
// async function fetchData() {
// try{
// const request = await axios.get(fetchUrl);
// console.log(request);
// return request;
// }
// catch (error){
// console.log(error);
// }
}
fetchData();
}, [fetchUrl]);
const opts = {
height: '390',
width: '100%',
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1,
},
};
//console.log(movies);
const handleClick = (movie) => {
if (trailerUrl){
setTrailerUrl('');
} else {
movieTrailer(movie?.name || "")
.then ((url) => {
const urlParams = new URLSearchParams(new URL(url).search);
setTrailerUrl(urlParams.get("v"));
}).catch(error => console.log(error));
}
};
return(
<div className="row">
<h2>{title}</h2>
<div className="row__posters">
{movies.map(movie => (
<img
key={movie.id}
onClick={() => handleClick(movie)}
className= {`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`${base_url}${isLargeRow ? movie.poster_path : movie.backdrop_path}`} alt={movie.name}/>
))}
</div>
{trailerUrl && <YouTube videoId="{trailerUrl}" opts={opts} /> }
</div>
)
}
export default Row
Invalid property name in movie
Taking a look at the tmdb docs it will show you what the properties of each object has. In this case, there is no name. Try using something like movie.title
In your handleClick() function you could use movie?.title.
Trying to use movie.name will give back a null value. Which errors out movieTrailer() and you get no YouTube url back.
Create handle function like this and the call it in your return function and use however you want... mainly should be used by using onClick method

Can't read properties of undefined React API call

I'm trying to build a loading component so that my app doesn't try and display my API data before it has loaded, causing a fatal error. Read a lot about componentWillMount function but this seems to have been deprecated. Trying to use setState to no avail.
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
const News = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const getNewsFromApi = async () => {
const response = await fetch(
"API_KEY_HERE"
);
const responseJson = await response.json();
console.log("json", responseJson);
setData(responseJson);
};
setLoading(false);
setInterval(getNewsFromApi, 1000)
}, []);
if (loading) {
return <h1> Data is loading...</h1>
}
return (
<div >
<Marquee gradientColor="" speed="120">
<h1>{data?.articles[0].title} - </h1>
<h1>{data?.articles[1].title} - </h1>
<h1>{data?.articles[2].title} - </h1>
<h1>{data?.articles[3].title} - </h1>
<h1>{data?.articles[4].title} - </h1>
<h1>{data?.articles[5].title} - </h1>
<h1>{data?.articles[6].title} - </h1>
<h1>{data?.articles[7].title} - </h1>
<h1>{data?.articles[8].title} - </h1>
</Marquee>
</div>
)
}
export default News
useEffect hook runs after the render (see docs). So what's going to happen:
You initialize loading as false (by calling useState(false)).
Component renders first time with loading=false so it skips loading placeholder and tries to immediately render articles list which is {}. It means data?.articles is undefined.
I would do the following changes:
Change your state declaration:
const [loading, setLoading] = useState(true);
This will make component to render loading placeholder first time.
2. Using state as an array of articles instead of object that is returned from your API also makes sense (as #Bru No suggested). But you'll need to make changes in your effect - something like:
setData(responseJson.articles);
you start read infos from state before the api response i suggest to use this solution
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
const News = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
(async function getNewsFromApi(){
const response = await fetch(
"API_KEY_HERE"
);
const responseJson = await response.json();
console.log("json", responseJson);
setData(responseJson);
})();
setLoading(false);
}, []);
if (loading) {
return <h1> Data is loading...</h1>
}
return (
<div >
<Marquee gradientColor="" speed="120">
{data.length > 0 && data.articles.map((item)=> <h1>{item.title} </h1>)}
</Marquee>
</div>
)
}
export default News

Firebase only fetches data on second or third attempt

I'm building an app with React and Firestore.
In one feature, I need to use some specific user data to query data from another collection, and show that on the app.
Specifically I want to use users.books, which returns an array, to query the collection books.
However, for some reason the users.books doesn't load on first render. It typically takes 2-3 renders to fetch the books.user data. This is despite the currentUserUID being loaded right away.
I've tried using a loading state as specified in How to wait for Firebase data to be fetched before progressing?, but to no avail.
Do I need to use the onSnapShot method?
Thanks for reading
My code
import 'firebase/firestore'
import { booksRef} from '../../App';
const ProfileScreen = ({ navigation }) => {
const currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
const [userBookTitles, setUserBookTitles] = useState([]);
const [userBooks, setUserBooks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getUserInfo(){
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
setUserBookTitles(dataObj.books)
console.log(userBookTitles)
}
}
getUserInfo();
}, [])
useEffect(() => {
async function getUserBooks() {
booksRef.where("title", "in", userBookTitles).onSnapshot(snapshot => (
setUserBooks(snapshot.docs.map((doc) => ({id: doc.id, ...doc.data()})))
))
}
setLoading(false);
getUserBooks()
}, [])
if (!loading) {
return (
<View style={styles.container}>
<Text> Hi {firstName} </Text>
<TouchableOpacity onPress={handlePress}>
<Text> Log out </Text>
</TouchableOpacity>
<Row
books={userBooks}
/>
</View>
);
} else {
return (
<View style={styles.container}>
<Text> Test </Text>
</View>
);
}
};
So it's worth noting that your setX methods may, or may not, complete in the sequence you have them in your code. Therefore, your booksRef call could be being made even though userBookTitles is an empty array. Which would explain why you're not getting any data on load.
You're setting userBookTitles in your first useEffect and the only other place I see you're using it is in your booksRef call. One easy fix would be to simple move booksRef inside the else statement of the first useEffect and simply pass it the userBookTitles there. This should help in solving your issue, if I understood it correctly.

React context values from consumer are always their initial value

I am using React context to pass down data, following the docs, however I am stuck with the initial value and am not sure what I did wrong.
This is what my context file looks like:
export const ItemsContext = createContext([]);
ItemsContext.displayName = 'Items';
export const ItemsProvider = ({ children }) => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const setData = async () => {
setLoading(true);
setItems(await getItemsApiCall());
setLoading(false);
};
useEffect(() => {
setData();
}, []);
console.warn('Items:', items); // This shows the expected values when using the provider
return (
<ItemsContext.Provider value={{ items, loading }}>
{children}
</ItemsContext.Provider>
);
};
Now, I want to feed in those items into my app in the relevant components. I am doing the following:
const App = () => {
return (
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
);
}
However, as my comment states, items will always be empty. On the other hands, if I do just use the ItemsProvider, I do get the proper value, but at this point need to access it directly on the app, so the ItemsContext.Consumer seems to make more sense.
Am I doing something wrong here?
Edit: A way around it seems to be to wrap the Consumer with the Provider, but that feels wrong and didn't see that at the docs. Is that perhaps the case?
So essentially, something like this:
const App = () => {
return (
<ItemsProvider>
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
</ItemsProvider>
);
}
You have to provide a ItemsContext provider above the App component hierarchy,otherwise the default value of the context will be used.
something in this form:
<ItemsContext.Provider value={...}>
<App/>
</ItemsContext.Provider>

Fetching data from Prismic API using React Hooks

I'm trying to query data from the Prismic headless CMS API and running into problems using React Hooks. The prismic API is returning null, though I know its being passed down correctly as I can query it successfully without using react hooks.
Heres my current compontent code. Its returning "cannot read property 'api' of null". It doesn't reach the 'data' console log.
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
}, []);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
It seems to be a case where on initial render prismicCtx is null and only on the subsequent render you receive the updated value. The solution is obviously to call the effect on change of prismicCtx, but you if you just want to call the api on initial render you would need to keep track of whether you called the api earlier or not which you can achieve by using useRef and also you don't need to set the state as empty if prismicCtx doesn't exist
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
const isFirstCall = useRef(true);
useEffect(() => {
if(prismicCtx && isFirstCall.current) {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
isFirstCall.current = false;
}
},[prismicCtx]);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
Figured it out, I beleive. PrismicCTX was being changed up the tree so it was switching to undefinded. A simple if/else fixed it and making it so it only updated on that prop change. Still not sure if best practice though!
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(
() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
if (prismicCtx) {
fetchLinks();
} else {
setLinks([]);
}
},
[prismicCtx]
);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;

Categories