This is the promise within a render() method of a React component.
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
let userName = snapshot.val().name;
});
I want to get the data of snapshot.val().name and put it in the
return(
<h1>{userName}</h1>
)
I could get the data out of the promise with an action dispatch to the redux store and then retrieving it where I need it but there should be a shorter way of achieving it I suppose? I tried different ways to do so but I failed due to the asynchronicity so... please help!
I haven't done React in a while, but I think it's:
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
let userName = snapshot.val().name;
this.setState({ userName: userName });
});
I was going to upvote the previous comment, but he failed to mention that doing this inside of the render method is a bad idea. That would make an infinite loop. Render -> promise resolves -> set state -> render -> repeat. You should do your firebase promise like this:
class Test extends React.Component{
constructor() {
super()
this.state = {}
}
componentDidMount() {
let { auth } = this.props //or wherever it comes from
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
this.setState({ userName: snapshot.val().name });
});
}
render() {
let { userName } = this.state
return (
<h1>{userName}</h1>
)
}
}
Related
Could you please have a look on the following code. I need to get some value from another class. This works asynchronous, so I provided a function handleGameDeserialization.
The function gets the right value (as I tested with the alert), however the setState function has no impact. Could that be a "this-context" issue?
export default class GameplayScreen extends React.Component {
constructor(props) {
super(props);
this.fbGame = new FBGame();
global.currentScreenIndex = 'Gameplay';
this.state = {
currentGame: 'N/A'
}
// this.handleGameDeserialization = this.handleGameDeserialization.bind(this);
if (this.props.route.params != null) {
this.gameKey = this.props.route.params.gameKey;
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
}
handleGameDeserialization = (game) => {
// alert('yeah'+game); // here comes the expected output
this.setState({
currentGame: game
});
}
render() {
return (
<View>
<Text>{this.state.currentGame}</Text>
</View>
/*<Board game={this.state.game}/>*/
)
}
}
I call that function when the component GameplayScreen is navigated to. As you can see above, there is a class FBGame, which does the deserialization (read the game from firebase database)
export default class FBGame {
...
deserializeGame(key, handleGameDeserialization) {
var gameRef = firebase.database().ref("games/"+key).child("serialized");
gameRef.on("value", snapshot => {
//console.log('deserialized: ' + );
handleGameDeserialization(snapshot.val().serialized);
});
}
...
}
edit:
When I use componentDidMount like below, it works fine. But this seems to be an anti-pattern. I still don't understand, why it doesn't work, when callded in the constructor and how I am supposed to solve this.
componentDidMount() {
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
For things like subscriptions that will update the state and other side-effects, you should put the logic out in componentDidMount() which will fire immediately after the component is mounted and won’t give you any trouble if you update the state inside of it.
You can't but things that call this.setState in the constructor.
I have just started learning JavaScript and am building an application using React.
I am struggling with an issue and would appreciate your help. I did come across similar questions but wasn't convinced with the answers and they didnt help me.
Please find my issue below . I am hiding the real name of the variables , components .
myService.js
export const findThisById = async Id=> {
const response = await fetch(`${URL}/${Id}`);
const json = await response.json();
return json;
};
myContainer.js // This is the parent component which has many chil component
.......
import {findThisById} from "../myService"
findThis= async Id=> {
const xyz= await findThisById(Id);
return xyz;
<Component1 findThis = {this.findThis}/>
......
Component1.js
const Component1 = ({findThis}) => (
<Component2 findThis = {findThis} id = {Id} // Id is being passed successfully
/>
)
Component2.js
class Component2 extends React.Component {
constructor(props) {
super(props);
console.log("Inside Constructor");
const something = this.props.findThis(this.props.id);
// course.then(data => console.log(data) // This works, i get the right data
)
console.log(something ); // Using this i get Promise state pending
}
// I need to use this value "something" inside a <h1></h1>
}
I have ommited all exports/imports but they work correctly
The network tab in the console also make a 200 request and the response sub tab shows the correct values.
Should i use .then when i call the function findThis? Why? I am using await for fetch and its response.
Please help me. Struggl
As mentioned in one of the comments async / await is just syntactic sugar for promises, so generally speaking you can either await or .then the result of findThis. That said, since you are calling this inside of a constructor function you won't be able to use await since the constructor isn't an async function.
One way to get the result of the Promise to render in your component would be to set the result in the component state and then render from the state. As mentioned by #Emile Bergeron in a follow up comment, you probably don't want to call setState in your constructor but instead in componentDidMount. This is to avoid calling setState on a potentially unmounted component.
class Component2 extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResult: ''
}
}
componentDidMount() {
this.props.findThis(this.props.id).then(searchResult => {
this.setState({
searchResult
})
});
}
render() {
return <div>{this.state.searchResult}</div>
}
}
Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
I can call data in console.log(this.state.eventUser); render return and its showing all of the data in eventUser. But when I try to call console.log(this.state.eventUser._id); its showing error of this Uncaught TypeError: Cannot read property '_id' of undefined. How can I solve this issue?
componentDidMount(){
Tracker.autorun(() => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
render(){
return(
<div>
{console.log(this.state.eventUser._id)}
</div>
);
}
Probably at some point this.state.eventUser in the render is undefined.
Try this
{this.state.eventUser && console.log(this.state.eventUser._id)
You can't use console.log inside JSX without enclosing in {}
Try below code
render(){
return(
<div>
{put javascript here}
</div>
);
}
Edit:
Your code is executing query on Database which is asynchronous
So you need to wait for it to complete.
Below is an implementation of async/await (es7+ features of javascript)
Try below code
componentDidMount(){
Tracker.autorun(async () => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = await Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
It looks like by the time your render() function runs, the user hasn't yet be saved to the component state.
To fix this, add a constructor to your component in which you define the eventUser as an empty object.
class MyClass extends Component {
constructor(props) {
super(props);
this.state {
eventUser: {}
};
}
componentDidMount() {
Tracker.autorun(() => {
Meteor.subscribe('allUsers');
const userId = this.props.location.state.event.userID;
const eventUser = Meteor.users.findOne({_id: userId});
this.setState({ eventUser });
});
}
render(){
console.log(this.state.eventUser._id)
return(
<div>My Test Page</div>
);
}
}
Hope that helps!
componentDidMount will fire after the component has already been mounted (as the name states). At the point where it reaches the enclosed statement the state has not been set yet.
I'm not sure why you are using a console.log inside of your render return, but if you are looking to actually display the user ID, conditional rendering is your friend: {this.state.eventUser && this.state.eventUser._id}
For the first time when your component will render, if you have knowledge about react lifecycle hooks, after constructor the render method will be triggered then componentDidMount hook.
so for the first render the eventUser is undefined then after componentDidMount the state will be fulfilled.
So solution is:
First don't put console.log in JSX, it is better to put it before return.
Second first check if the object is defined then return your data
Code:
render(){
const { eventUser } = this.state;
// if eventUser is defined then log it
{eventUser && console.log(eventUser._id)}
// or you can add very simple load state
if(!eventUser) return <h1>Loading....</h1>
return(
<div>
{probably info about user }
</div>
);
}
COnsider the following:
componentDidMount(){
this.props.requestUser(this.props.match.params.userID).then(res =>
{
this.pageOwner = res.user;
debugger;
})
}
Why is my this.pageOwner still returning undefined? I have been trying my as off to get a user into a state that my user profile can actually acces.
If you're trying to access this.pageOwner, you need to do so after the this.props.requestUser() async function has completed. You're currently getting the user in componentDidMount(), so your render() will initially see this.pageOwner as undefined until the async call is finished.
Additionally, it might be better to store the result of your async call in state so that your component will re-render once the value is filled.
As #colin said, put it in this.state. If you are getting an error in the render() function, then just make your render like so:
constructor() {
super();
this.state = {
pageOwner: null
}
}
componentDidMount() {
this.props.requestUser(this.props.match.params.userID).then(res =>
{
this.setState({pageOwner: res.user});
debugger;
})
}
render() {
if(this.state.pageOwner)
return ( ... )
else
return null;
}