I'm using React + Flux on the frontend for a project and I need to get the username to display it on the sidebar.
The problem: I call the action in the constructor of the es6 class which fetches the data needed from the API, and I can see it being logged to the console from the onUserStateChanged method, but it doesn't get updated in the state within the render method. I don't understand how I can get the state change reflected in the component.
export default class extends React.Component {
constructor() {
super();
UserActions.getCurrentUser();
this.state = {
user: {}
};
}
componentDidMount() {
UserStore.addChangeListener(this.onUserStateChange);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onUserStateChange);
}
onUserStateChange() {
console.log('called');
this.state = {
user: UserStore.getCurrentUser()
};
console.log(this.state);
}
render(){
var _this = this;
return (
console.log(this.state);
<div>{_this.state.user.username}</div>
);
}
}
The call to console.log from onUserStateChange() contains the correct state back from the API whereas the console.log in render just shows a blank JS object
You probably want to use setState
As documentation says:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Also your constructor seems strange, do you really intend to not use the result of UserActions.getCurrentUser(); in
UserActions.getCurrentUser();
this.state = {
user: {}
};
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.
In my App component I call checkUserAuth() method in order check if user is logged in or not and render different content according to this. It works fine when I call it via componentDidMount() method however it doesn't work If I try to call it via another method:
TypeError: this.checkUserAuth is not a function
In my code:
class App extends React.Component {
constructor() {
super();
this.state = {
loggedIn: false
};
this.checkUserAuth = this.checkUserAuth.bind(this);
}
componentDidMount(){
this.checkUserAuth(); // here this method can be called
}
checkUserAuth(){
const loggedUser = AuthService.isAuthenticated();
if(loggedUser){
store.dispatch(actions.loginSuccess());
this.setState({loggedIn: true});
}
}
logoutUser(){
store.dispatch(actions.logout());
this.checkUserAuth(); // here it return errors
}
How can I fix it?
put this in the constructor:
this.logoutUser = this.logoutUser.bind(this);
Also, consider using an auto-binding library if you find it tedious to always bind stuff. I use react-autobind
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 have this code:
export default class FinancesPage extends React.Component {
constructor(props) {
super(props);
this.state = {users: []};
}
componentWillMount() {
firebase.database().ref('Users').orderByChild('transactions').startAt(1).on('value', snap => {
const users = arrayFromObject(snap.val());
this.setState({users: users});
});
}
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.state.users}/>
</div>
)
}
}
And this code:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
componentWillMount() {
const users = this.state.users;
// Do stuff here
}
render() {
return (
{/*And render stuff here*/}
);
}
}
What's happening right now: The parent element FinancesPage passes an empty array of users to the child NumberOfPurchasesComponent. I need it to pass a new value of the array every time there is an update.
And i want to pass the users from FinancesPage to NumberOfPurchasesComponent, but users data is obtained async. How can I make the NumberOfPurchasesComponent refresh when the variable value is obtained?
Have you tried to use componentWillReceiveProps? I mean something like:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state={users: []}
}
componentWillReceiveProps(nextProps) {
if(nextProps.users && nextProps.users!==this.state.users){
this.setState({
users: nextProps.users
})
}
}
render() {
return (
{/*And render stuff here*/}
);
}
}
This way the component knows when it has to re-render.
The FinancesPage implementation looks good. The problem lies in NumberOfPurchasesComponent in this particular piece of code :
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
Am assuming in the render method of NumberOfPurchasesComponent you are using this.state.users instead of this.props.users.
constructor runs only once. Now as you mentioned data is fetched async, which means NumberOfPurchasesComponent is initially rendered even before the the response is obtained. Hence its constructor method which runs only once sets the users state to []. Even if the props gets updated from FinancesPage, as the render in NumberOfPurchasesComponent uses state, no re-render happens.
Try using this.props.users directly in NumberOfPurchasesComponent render and see if it works.
As per FinancesPage page it is well and good with codebase, but problem is why you are making setstate if there is no any manipulation of user's data as you got from API call.
So without making setState just pass it as direct
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.props.users}/>
</div>
)
}
so whenever the API calls to fetch the response, here update value get in passed to NumberOfPurchasesComponent class.
In React, are there any real differences between these two implementations?
Some friends tell me that the FirstComponent is the pattern, but I don't see why. The SecondComponent seems simpler because the render is called only once.
First:
import React, { PropTypes } from 'react'
class FirstComponent extends React.Component {
state = {
description: ''
}
componentDidMount() {
const { description} = this.props;
this.setState({ description });
}
render () {
const {state: { description }} = this;
return (
<input type="text" value={description} />
);
}
}
export default FirstComponent;
Second:
import React, { PropTypes } from 'react'
class SecondComponent extends React.Component {
state = {
description: ''
}
constructor (props) => {
const { description } = props;
this.state = {description};
}
render () {
const {state: { description }} = this;
return (
<input type="text" value={description} />
);
}
}
export default SecondComponent;
Update:
I changed setState() to this.state = {} (thanks joews), However, I still don't see the difference. Is one better than other?
It should be noted that it is an anti-pattern to copy properties that never change to the state (just access .props directly in that case). If you have a state variable that will change eventually but starts with a value from .props, you don't even need a constructor call - these local variables are initialized after a call to the parent's constructor:
class FirstComponent extends React.Component {
state = {
x: this.props.initialX,
// You can even call functions and class methods:
y: this.someMethod(this.props.initialY),
};
}
This is a shorthand equivalent to the answer from #joews below. It seems to only work on more recent versions of es6 transpilers, I have had issues with it on some webpack setups. If this doesn't work for you, you can try adding the babel plugin babel-plugin-transform-class-properties, or you can use the non-shorthand version by #joews below.
You don't need to call setState in a Component's constructor - it's idiomatic to set this.state directly:
class FirstComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.initialX
};
}
// ...
}
See React docs - Adding Local State to a Class.
There is no advantage to the first method you describe. It will result in a second update immediately before mounting the component for the first time.
Update for React 16.3 alpha introduced static getDerivedStateFromProps(nextProps, prevState) (docs) as a replacement for componentWillReceiveProps.
getDerivedStateFromProps is invoked after a component is instantiated as well as when it receives new props. It should return an object to update state, or null to indicate that the new props do not require any state updates.
Note that if a parent component causes your component to re-render, this method will be called even if props have not changed. You may want to compare new and previous values if you only want to handle changes.
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is static, therefore it does not have direct access to this (however it does have access to prevState, which could store things normally attached to this e.g. refs)
edited to reflect #nerfologist's correction in comments
You could use the short form like below if you want to add all props to state and retain the same names.
constructor(props) {
super(props);
this.state = {
...props
}
//...
}
YOU HAVE TO BE CAREFUL when you initialize state from props in constructor. Even if props changed to new one, the state wouldn't be changed because mount never happen again.
So getDerivedStateFromProps exists for that.
class FirstComponent extends React.Component {
state = {
description: ""
};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.description !== nextProps.description) {
return { description: nextProps.description };
}
return null;
}
render() {
const {state: {description}} = this;
return (
<input type="text" value={description} />
);
}
}
Or use key props as a trigger to initialize:
class SecondComponent extends React.Component {
state = {
// initialize using props
};
}
<SecondComponent key={something} ... />
In the code above, if something changed, then SecondComponent will re-mount as a new instance and state will be initialized by props.
set the state data inside constructor like this
constructor(props) {
super(props);
this.state = {
productdatail: this.props.productdetailProps
};
}
it will not going to work if u set in side componentDidMount() method through props.
If you directly init state from props, it will shows warning in React 16.5 (5th September 2018)
you could use key value to reset state when need, pass props to state it's not a good practice , because you have uncontrolled and controlled component in one place. Data should be in one place handled
read this
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
You can use componentWillReceiveProps.
constructor(props) {
super(props);
this.state = {
productdatail: ''
};
}
componentWillReceiveProps(nextProps){
this.setState({ productdatail: nextProps.productdetailProps })
}