Can't await promise in map method - javascript

I'm trying to render a list of rooms and then get the user data of each recipient in the room, and since there only can be 2 users in the room (you + the other person), the recipient is just the other person.
async renderRooms(room, user) {
const recipentId = room.users.filter(id => id != user.id)[0],
recipients = await userService.getOne(recipentId);
console.log(recipients)
return (
<ListGroupItem tag="a" href="#" data-unread="3" action key={room._id} onClick={this.onRoomOpened.bind(this, room._id)}>
<img src={""} />
<div className="details">
<div className="recipient-name"></div>
<div className="last-message">
<MessageCircle className="feather" />
This is an example last message...
</div>
</div>
</ListGroupItem>
);
}
render() {
if(this.props.rooms.length < 1) return <LoadingPage />;
return (
<userContext.Consumer>
{({user}) => {
if(this.state.roomId) return <ActiveRoom id={this.state.roomId} />;
else
return (
<ListGroup className="recipients">
{this.props.rooms.map(room => this.renderRooms(room, user))}
</ListGroup>
);
}}
</userContext.Consumer>
);
}
This way I filter out my own user ID from the user array, and then I take the remaining ID and use a promise to get the user data for it. But the problem here is that whenever I call await it throws the error:
react-dom.development.js:14748 Uncaught Error: Objects are not valid
as a React child (found: [object Promise]). If you meant to render a
collection of children, use an array instead.
If I try to use .then() it actually doesn't seem to be called at all, as in, if I put a console.log in there, it prints out nothing.
Any idea how I can accomplish my task?

You can't call an async function in the render method.
Async functions returns a Promise object, so this
{this.props.rooms.map(room => this.renderRooms(room, user))}
Will return an array of Promise Objects and you can't render and Object.
You need to call your async function in componentDidMount and render it when the data is loaded.
Check this answer for an example of what to do.

Related

React - Context Provider - Calling Async Function Inside Consumer Returns Nothing After Promise is resolved

I'm trying to call an async function from the provider and then show content based on the response but nothing is returned. Can someone please advise what am I doing wrong?
Below is the code:
const App = () => {
return (
<>
<Header />
<UserConsumer>
{
({getProfile}) => {
getProfile.then(profile) => {
return profile && (
<h3>{profile.name}</h3>
)
}
}
}
</UserConsumer>
</>
)
}
I can see getProfile returns a promise with profile data and even if I remove profile && nothing shows. I have used other functions the same way. getProfile is the only async function and not sure why am I having this issue.
Any ideas?
Thanks
Suppose your UserConsumer's rendering logic is something like this:
<div>{props.children({getProfile})}</div>
Since you are not returning anything in this function:
({getProfile}) => {
getProfile.then(profile) => {
return profile && ( // this is return value of then callback, not props.children
<h3>{profile.name}</h3>
)
}
}
Everytime React renders UserConsumer, the final JSX to be rendered is this:
<div>{undefined}</div>
The main problem of your code, You are not supposed to do asynchronous operations in return sections of React Components.
If you need to render some UI based on server side data (like profile data), you need to fetch data in useEffect, then put the received data in some state, and use the state in return of the component.

Returning a component from a map function

I want to create a function that maps throw a firestore collection and return a Component for each document in the collection I have tried to use the code below
<div className="posts">
{
db.collection("posts")
.get()
.then((snapshot) => {
snapshot.docs.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />
))})}
</div>
but this error shows:
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.ode
JSX is not the place to make Ajax calls. Instead make the call inside componentDidMount/useEffect and set the data received inside state. As soon as state is updated with received data, React will automatically re-render the component and display the expected content. Try something as follows:
const [snapshots, setSnapshots] = useState();
useEffect(()=> {
db.collection("posts")
.get()
.then((snapshot) => {
setSnapshots(snapshot.docs)
}
)
}, []);
render
<div className="posts">
{snapshots && snapshots.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />
)
}
</div>
Since you haven't given a hint as to whether you're using hooks or not, here is how you should deal with asynchronous stuff and setting states in React (using hooks) :-
function MyComponent () {
const [snapshot,setSnapshot] = useState();
useEffect(()=>{
async function getPosts(){
let snapshot = await db.collection("posts").get();
setSnapshot(snapshot);
}
getPosts();
},[])
return
(<div className="posts">
snapshot?.docs?.map((doc)=>(
<PostContect img={doc.data().image} Admin={doc.data().admin} Date={"January 14, 2019"} CommentsNo={"2"} Title={doc.data().title} Body={doc.data().title} />)
</div>)
}
What you are doing is returning Promise object as the error states. That async stuff is taken care either inside a useEffect,event handler or a custom-hook. There could be more ways but this is a general point to start.
As the error message states Promise is not a valid React child. To solve that issue you can introduce a state which keeps the result of db.collection('posts').get() method. Then iterating through on that array with your .map() which will be a valid React child.
See a possible solution for your scenario:
// in the root of you component, introduce a state
const [posts, setPosts] = useState([])
// here in useEffect you can do your API call and update the state
useEffect(() => {
db.collection("posts")
.get()
.then((snapshot) => {
setPosts(snapshot.docs)
})
}, [])
// ... rest of your component's code
return (
<div className="posts">
{
posts && posts.map((doc, i) => (
<div key={i}>
doc.data().title
</div>
// here you can use <PostContect /> component
// either doc.data().title or doc.title to get the value
))
}
</div>
)
Suggested reads:
Using the Effect Hook
Using the State Hook
The code you wrote returns a promise, which can't be rendered by react. You can use conditional rendering to return something else while the data is being fetched.
Check this question for more info

How to map list and implement async within it?

renderDuration(x) {
return 'abc'
}
render() {
return (
<View>
{this.props.list.map(x => (
<Text>{this.renderDuration(x)}</Text>
))}
</View>
)
}
The above code is working perfectly fine. The situation is very basic which is looping the list and for each of the element, call the method renderDuration and get the individual string value. Now take a look below.
async renderDuration(x) {
let someAsyncOpt = await getTextFromSomewhere();
return someAsyncOpt;
}
So once the same method we change it to async method, it breaks and hitting exception
Invariant Violation: Invariant Violation: Objects are not valid as a React child (found: object with keys {_40, _65, _55, _72}). If you meant to render a collection of children, use an array instead.
I understand that a viable option is to get whatever data that's needed first, instead while render. This question is basically trying to explore the possibility to perform async operation while mapping, if it make sense?
UPDATES:
I've included the below code to show that it has nothing to do with wrong type of returning from async opt. It's basically the moment we include the keyword async, it will break
async renderDuration(x) {
return 'abc';
}
(Update) try to use this:
class Alpha{
// ...
// Update :
async renderDuration(x) {
let someAsyncOpt = await Promise.all(getTextFromSomewhere());
return someAsyncOpt;
}
render() {
return (
// Old :
<View>
{this.props.list.map(x => (
<Text>{this.renderDuration(x)}</Text>
))}
</View>
// Update :
<View>
{
this.props.list.map(
async (x) => { await this.renderDuration(x) }
);
}
</View>
)
}
}

Cannot retrieve single item from an array stored in the state of React

Before I get bashed: "I am new to React!"
The url to the .json file is:
JSON FOR GETTING QUOTES
I am maintaining a state in my app which holds an array of quotes and the key value inside it.
constructor(props){
super(props);
this.state = {
quotes: [],
key: 0
};
this.handleClick= this.handleClick.bind(this)
}
The state is updated on getting a request to the url using axios.
componentDidMount() {
const url = 'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json';
axios.get(url).then((res) =>{
const items = (res.data.quotes)
this.setState({quotes: items})
console.log(this.state)
}).catch(err => console.log(err))
}
Then I wish to pass get only one quote each time a click is made on the button.
For which I render a "DivElement" passing a specific quote as a prop as following:
render() {
return (
<div>
<button onClick={this.handleClick}>
Click Me
</button>
<DivElement
currentquote = {this.state.quotes[this.state.key].quotes}
/>
</div>
);
}
"DivElement" declaration:
function DivElement(props){
console.log(props.currentquote)
return <p>{props.currentquote}</p>}
This is the TypeError I get:
TypeError: this.state.quotes[this.state.key] is undefined
Things I have tried:
Didn't work Use JSON.parse method to set the state.
What I wish to do: To display a random quote based on random key generated by onClick method.
No render is asynchronous and it doesn't wait for API callback.
you have to conditionally render your component like this:
render() {
return (
this.state.quotes.length && ( <div>
<button onClick={this.handleClick}>
Click Me
</button>
<DivElement
currentquote={this.state.quotes[this.state.key].quotes}
/>
</div> )
);
}
}
Please let me know if the issue still persists,

Rendering an array of objects React

Good afternoon,
I am trying to display data that is provided to my application by an instance of MongoDB before the initial render. I have yet been successful in doing so either running into errors or warnings.
This is the part of the render method I am working with.
<div className="right-column">
<div className="pipeline">
{this.props.tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
})}
</div>
</div>
this.props.tournamentList has a value of an array of objects like so:
tournamentList:
Array[15]
0:
{…}
1:
{…}
2:
{…} ...
This list comes to my application through the componentWillMount lifecycle method, so before the initial render. To me I should be able to iterate through the array and make a dynamically generated list of tournaments provided by my database.
Yet with the code I provided I am getting this warning:
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {prelims, outRounds, notes}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method ofBuildTournament.
I tried this approach, creating called displayTournaments and calling it inside the div with the class of "pipeline" but nothing happens, no errors no render:
displayTournaments(){
const { tournamentList } = this.props;
tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
})
}
Clearly I am doing something wrong but I don't know what. Is this an instance where I should be using keyed fragments as suggested by the error message? Would anyone smarter than myself be willing to lend some insight?
Cheers.
Update:
Sorry, I misunderstood your question. Kyle is correct with the loading state.
In addition, using a library like lodash will allow you to map over objects in a more natural manner. The native javascript map method doesn't handle objects all that well.
https://www.npmjs.com/package/lodash
you use it much the same way. just
import _ from lodash
then
_.map(objectToMap, (x) => <Component key={x}>{x.thing}</Component>)
Here would be a simple solution that would have a loading state, error state, and success state.
The first thing to note is you will need to use Object.keys() to your object in order to map over the array of keys since you cannot map plain objects. You should also note that the map will return the key of each object so in order to target key values pairs you will need to use a syntax like this tournaments[key].name rather than just doing tournament.name as you are targeting an object with in an object and then grabbing the value.
Let me know if you need any more help with this
import React from 'react'
import Loader from '../Loader'
const resultList = ({ tournaments, isFetching = true }) => (
<div>
{
isFetching
? <div>
<Loader /><br />
<span>Loading…</span>
</div>
: <div>
{
Object.keys(tournaments).length
? <div>
{
tournaments.map((key) => (
<section id={tournaments[key].id} key={key}>
<p>{tournaments[key].name}</p>
</section>
))
}
</div>
: <div>
<p>There are no tournaments....</p>
</div>
}
</div>
}
</div>
);
export default resultList
You are going to need to have a loading state if you get your data in the componentWillMount or componentDidMount lifecycle hooks. The below example will illustrate how this is done.
class ComponentThatGetsAsyncData extends PureComponent {
constructor( props ) {
super( props );
this.state = {
tournamentList: [ ]
}
}
componentDidMount() {
// use any http library you choose
axios.get( "/some_url" )
.then( ( res ) => {
// this will trigger a re-render
this.setState({
tournamentList: res.data
});
})
.catch( ( err ) => {
// handle errors
});
}
render() {
const { tournamentList } = this.state;
// i'd use something other than index for key
// on your initial render your async data will not be available
// so you give a loading indicator and when your http call
// completes it will update state, triggering a re-render
return (
{
tournamentList ?
tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
}) :
<div className="loading">Loading...</div>
}
);
}
}

Categories

Resources