React map function does not execute when the component is rendered - javascript

As you can see below in the dev tools screen shot, the child element does have props. My issue is I cannot get them to appear in the DOM when the component is first rendered. I have to click on the Link element again to re-render the component and only then does the map function work correctly (second screen shot). Another thing is I am using the same code in another component and it works fine. Help!
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import NewsLetterListChildComponent from './children/NewsLetterListChildComponent';
import LoadingComponent from '../Loading/LoadingComponent';
function PublicNewsLetterListComponent({ user }) {
const [ newsLetters, setNewsLetters ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ errors, setErrors ] = useState(false);
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
let newsLetterArray = [];
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
listRef
.getDownloadURL()
.then(url => {
newsLetterArray.push({ id: doc.id, data: doc.data(), image: url });
})
.catch(error => console.log(error))
});
});
setNewsLetters(newsLetterArray);
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);
const renderContent = () => {
if(loading) {
return <LoadingComponent />
} else {
return <NewsLetterListChildComponent newsLetters={newsLetters} />
}
}
return renderContent();
}
export default PublicNewsLetterListComponent
import React from 'react';
import { ListGroup, ListGroupItem, Row, Col } from 'reactstrap';
function NewsLetterListChildComponent({ newsLetters }) {
return (
<div>
<Row>
<Col md={{size: 6, offset: 3}}>
<ListGroup>
{newsLetters.map((item, index) => {
return (
<ListGroupItem key={index} className="m-1" ><h1>{item.data.title} </h1><img src={item.image} alt={item.data.title} className="thumb-size img-thumbnail float-right" /></ListGroupItem>
);
})}
</ListGroup>
</Col>
</Row>
</div>
)
}
export default NewsLetterListChildComponent;
Initial render and the list group is empty
after the re-render and now the list group is populated

You need to call setNewsLetters when the data is resolved:
const getNewsLetters = async () => {
setLoading(true);
try {
const newsLetters = await firebase
.firestore()
.collection("newsLetters")
.get();
const data = await Promise.all(
newsLetters.docs.map(async (doc) => {
const url = await firebase
.storage()
.ref()
.child("newsLetterImagesRef/" + doc.id)
.getDownloadURL();
return {
id: doc.id,
data: doc.data(),
image: url,
};
})
);
setNewsLetters(data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};

The useEffect code contains an async request and you are trying to update an array of newsLetters in state even before it will be fetched. Make use of Promise.all and update the data when it is available
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
const promises = querySnapshot.map((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
return listRef
.getDownloadURL()
.then(url => {
return { id: doc.id, data: doc.data(), image: url };
})
.catch(error => console.log(error))
Promise.all(promises).then(newsLetterArray => { setNewsLetters(newsLetterArray);})
});
});
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);

If you check newletters with if, your problem will most likely be resolved.
review for detail : https://www.debuggr.io/react-map-of-undefined/
if (newLetters){
newLetters.map(item=> ...)
}

Related

Reactjs Empty State when Page Load

API: https://developers.themoviedb.org/
const [searchQuery, setSearchQuery] = useState();
const [searchResults, setsearchResults] = useState([]);
const getSearchResults = () => {
baseService.get(`/search/multi?api_key=${API_KEY}&language=en-US&query=${searchQuery}`)
.then(data=>setsearchResults(data.results))
}
useEffect(() => {
getSearchResults()
console.log(searchResults)
}, [searchQuery])
return (
<Container>
<TextField color="primary" label="Search for anything" size="small" onChange={(e) => setSearchQuery(e.target.value)}/>
{searchResults && searchResults.map((search,key) => (
<span key={key}>{search?.title}</span>
))}
</Container>
baseservice.js is like that
import { API_URL } from "./config"
export const baseService = {
get: async (url) => {
let response = [];
await fetch(API_URL+url, {
})
.then((res) => res.json())
.then((data) => {
response = data
})
return response;
}
}
1.Picture is when page load.
2.Picure is when entry search term.
In baseService.get you’re returning response before the promise has resolved, it can be simplified to this:
export const baseService = {
get: (url) => {
return fetch(API_URL+url)
.then((res) => res.json())
}
}
This happens because you're logging searchResults inside a useEffect that has a searchQuery dependency, so it logs the first time state are set and then it doesn't log when it changes from fetch response. If you wanna log it correctly you have to add searchResults as a dependecy.
useEffect(() => {
getSearchResults()
console.log(searchResults)
}, [searchQuery, searchResults])

React Native Undefined is not an object (evaluating 'userData.id)

I'm new to react native, I have a personal project, I am trying to get data from Firestore cloud, but I keep getting this error on the screen change.
It works fine when I comment out the database code, so I'm wondering what could be the cause.
My code
import React from "react";
import auth from "#react-native-firebase/auth";
import firestore from "#react-native-firebase/firestore";
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData(prev => {
return { ...prev, uid: usr.uid };
});
}, []);
const userRef = firestore().collection("users");
const snapshot = userRef
.where("uid", "==", userData.uid)
.onSnapshot()
.then(console.log(uid))
.catch(error => {
Alert.alert(error.message);
});
const [userData, setuserData] = React.useState({
uid: ""
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
You can try below code
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=>{
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
fetchdata()
}, [userData]);// Once userData value has been updated then only call fetchData()
const fetchdata = ()=>{
const userRef = firestore().collection('users').doc(userData.uid).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document found!");
console.log(doc.data())
} else {
console.log("No such document!");
}
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;
#Maheshvirus is right. But I think you have tried to fetch data when userData.uid is not empty.
Try this way if looking for such a way.
import React from 'react';
import auth from '#react-native-firebase/auth';
import firestore from '#react-native-firebase/firestore';
const ProfileStackScreen = ({ navigation }) => {
React.useEffect(() => {
const usr = auth().currentUser;
setuserData((prev)=> {
return {...prev,uid: usr.uid};
});
}, []);
React.useEffect(() => {
if(userData.uid !== ''){
getData()
}
}, [userData]);
const getData = () => {
firestore()
.collection('users');
.where('uid', '==', userData.uid)
.onSnapshot()
.then(() => {
console.log(uid)
})
.catch((error)=> {
Alert.alert(error.message);
});
}
const [userData, setuserData] = React.useState({
uid: '',
// other field go here
});
return (
<View>
<Text>{userData.uid}</Text>
</View>
);
};
export default ProfileStackScreen;

How to wait until context value is set?

I'm trying to render a header.
First, in InnerList.js, I make an API call, and with the data from the API call, I set a list in context.
Second, in Context.js, I take the list and set it to a specific data.
Then, in InnerListHeader.js, I use the specific data to render within the header.
Problem: I currently get a TypeError undefined because the context is not set before rendering. Is there a way to wait via async or something else for the data to set before loading?
My code block is below. I've been looking through a lot of questions on StackOverflow and blogs but to no avail. Thank you!
InnerList.js
componentDidMount() {
const { dtc_id } = this.props.match.params;
const {
setSpecificDtcCommentList,
} = this.context;
MechApiService.getSpecificDtcCommentList(dtc_id)
.then(res =>
setSpecificDtcCommentList(res)
)
}
renderSpecificDtcCommentListHeader() {
const { specificDtc = [] } = this.context;
return (
<InnerDtcCommentListItemHeader key={specificDtc.id} specificDtc={specificDtc} />
)
}
Context.js
setSpecificDtcCommentList = (specificDtcCommentList) => {
this.setState({ specificDtcCommentList })
this.setSpecificDtc(specificDtcCommentList)
}
setSpecificDtc = (specificDtcCommentList) => {
this.setState({ specificDtc: specificDtcCommentList[0] })
}
InnerListHeader.js
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
In general, you should always consider that a variable can reach the rendering stage without a proper value (e.g. unset). It is up to you prevent a crash on that.
For instance, you could rewrite you snippet as follows:
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{Boolean(specificDtc.dtc_id) && specificDtc.dtc_id.dtc}
</div>
</div>
);
}
When you make an api call you can set a loader while the data is being fetched from the api and once it is there you show the component that will render that data.
In your example you can add a new state that will pass the api call status to the children like that
render() {
const { specificDtc, fetchingData } = this.props;
if (fetchingData){
return <p>Loading</p>
}else{
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
}
``
in my case, i am calling external api to firebase which lead to that context pass undefined for some values like user. so i have used loading set to wait untile the api request is finished and then return the provider
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
updateProfile
} from 'firebase/auth';
import { auth } from '../firebase';
import { useNavigate } from 'react-router';
import { create_user_db, get_user_db } from 'api/UserAPI';
import { CircularProgress, LinearProgress } from '#mui/material';
import Loader from 'ui-component/Loader';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
const [user_db, setUserDB] = useState();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const createUser = async (email, password) => {
const user = await createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const googleSignIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const logout = () => {
setUser();
return signOut(auth).then(() => {
window.location = '/login';
});
};
const updateUserProfile = async (obj) => {
await updateProfile(auth.currentUser, obj);
return updateUser(obj);
};
const updateUser = async (user) => {
return setUser((prevState) => {
return {
...prevState,
...user
};
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setLoading(true);
if (currentUser) {
const user_db = await get_user_db({ access_token: currentUser.accessToken });
setUserDB(user_db);
setUser(currentUser);
setIsAuthenticated(true);
}
setLoading(false);
});
return () => {
unsubscribe();
};
}, []);
if (loading) return <Loader />;
return (
<UserContext.Provider value={{ createUser, user, user_db, isAuthenticated, logout, signIn, googleSignIn, updateUserProfile }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};

How to resolve this React Promise race condition problem

I'm having an issue where I am unable to set the state using "setPhotos" without setting up a "setTimeout()". When I console.log I get the data, but when changing the console.log(newArray) to setPhotos(newArray) the state is not updated / rendered.
setTimeout(() => {
setPhotos(newArray);
}, 1000);
Any help would be so greatly appreciated. Maybe I am fundamentally using Promises wrong..?
import React, { useState, useEffect } from "react";
import { useParams, useLocation } from "react-router-dom";
import { Table } from "react-bootstrap";
import Firebase from "../../../config/Firebase";
import "./QuoteRequest.scss";
const QuoteRequest = () => {
const { request } = useLocation().state;
console.log(request);
const [photos, setPhotos] = useState([]);
useEffect(() => {
let newArray = [];
Firebase.storage()
.ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
data.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
newArray.push(url);
});
});
})
.then(() => {
setTimeout(() => {
setPhotos(newArray);
}, 1000);
});
}, []);
return (
<div className="perform mt-4">
<h1>Perform Quote</h1>
<Table bordered></Table>
<h1>Quote here v</h1>
{photos.length ? (
photos.map((photo) => (
// <span>img</span>
<img src={photo} className="photo" />
))
) : (
<span>no</span>
)}
</div>
);
};
export default QuoteRequest;
UPDATE:
I was able to get the result I needed with the following:
useEffect(() => {
const promises = [
Firebase.storage()
.ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
return data.items.map((imageRef) => {
return imageRef.getDownloadURL();
});
}),
];
Promise.all(promises).then((data) => {
// setPhotos(urls);
let newArray = [];
data[0].map((items) => {
items.then((url) => {
console.log(url);
newArray.push(url);
setPhotos(newArray);
});
});
});
}, []);
Try using Promise.all
const promises = Firebase.storage().ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
return data.items.map((imageRef) => {
return imageRef.getDownloadURL()
});
})
Promise.all(promises).then((urls) => {
setPhotos(urls);
});
}, []);

Get data from Axios request

I have an api that returns the following data
[{…}]
0: {id: 1, postId: 86, commentBody: "This is a test comment", giphyUrl: "https://media2.giphy.com/",
postPicture: "pic.com", …}
length: 1
__proto__: Array(0)
[{"id":1,"postId":86,"commentBody":"This is a test comment","giphyUrl":"https://media2.giphy.com/","postPicture":"pic.com","userId":1,"userIdto":2,"userIdName":"Elton","userIdtoName":null}]
I want to access the comment body but when i do something like data.commentbody or data[0].commentbody i dont get the value back it returns undefined. please help, below is my axios request.
const fetchComments = async (id) => {
try {
return await axios.get('http://10.6.254.22:5000/comments/' + id)
} catch (error) {
console.error(error)
}
}
const comments = async(id) => {
const fetchedComments = await fetchComments(id);
console.log(fetchedComments.data)
// console.log(fetchedComments.data.message)
return fetchedComments.data
}
And then i want to send it as a prop to my react component
const reversedProps = this.props.posts.reverse();
const postItems = reversedProps.map(post => (
console.log('post id is===' + post.id),
comments(post.id),
<PostBodyTemplate key={post.id} title={post.title} postBody={post.postBody} giphyUrl =
{post.giphyUrl} userWhoPosted={post.userIdName}/>
));
You need to send your data to your component like this:
comments.map(comment => {
return <PostBodyTemplate key={post.id} comment={comment} />;
});
A more complete example:
import React, { useState, useEffect } from "react";
function App() {
const [comments, setComments] = useState([]);
useEffect(() => {
const getComments = async () => {
const response = await axios.get("http://10.6.254.22:5000/comments/1");
setComments(response.data);
};
getComments();
}, []);
return (
<div>
{comments.map(comment => {
console.log(comment.commentBody); // => you can access the commentBody like this
return <PostBodyTemplate key={post.id} comment={comment} />;
})}
</div>
);
}
export default App;

Categories