I'm trying to set the state of my PlayerKey component here however the state won't update on a onClick action:
class PlayerKey extends Component {
constructor(props) {
super(props);
this.state = {
activeKeys:[]
}
}
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
})
}
render() {
return (
<div className="key" data-row-number={this.props.rowKey} data-key-number={this.props.dataKeyNumber} onClick={this.activateKey}></div>
)
}
}
I've tried console logging this.state in activateKey and it gives me the state of the component no problem (the blank array) so not sure why I can't update it?
setState method in react is asynchronous and doesn't reflect the updated state value immediately.
React may batch multiple setState() calls into a single update for performance.
So accessing the recently set state value might return the older value. To see whether the state has really been set or not, You can actually pass a function as callback in setState and see the updated state value. React Docs
As in your case, you can pass a function as callback as follows.
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
}, () => {
console.log(this.state.activeKeys); // This is guaranteed to return the updated state.
});
}
See this fiddle: JSFiddle
Related
I have been breaking my head over this issue for the past 2-3 days. I am trying to show a new user's displayName set and stored on the Firebase Authentication database upon the load of their account page. I can not seem to get it showing upon the first load. I tried the setState way of doing it within the UserAccount component itself but it causes the same issue. The page needs to be manually refreshed for the username state to change and the p element to show the displayName in the screen. Please help me figure this out. Here's the code of the UserAccount component. If you need other codes from other components, please let me know.
class UserAccount extends React.Component {
constructor(props) {
super(props)
this.state = {
username: this.props.user.displayName
}
this.signout = this.signout.bind(this);
}
signout() {
firebase.auth().signOut()
}
The following commented code is my first try to get the displayName to show but it had the same issue so I tried to pass the props from the App.js component to see if it would work but it again has the same issue:
// componentDidMount () {
// firebase.auth().onAuthStateChanged((user) => {
// if (user) {
// var displayName = user.displayName;
// console.log(displayName);
// this.setState((state) => {
// return {username: displayName} })
// } else {
// console.log('Please sig in')
// }
// })
// }
render () {
return (
<div>
<h1> You are in </h1>
<button onClick={this.signout}>Sign Out</button>
<p>Hey {this.state.username}</p>
</div>
)
}
}
In your code you are using props value to initialize state in constructor. Since child component is already rendered, changing the parent prop doesn't call constructor of the child component. That's why it doesn't change the username and you need to forcefully trigger an update either by calling setState or forceUpdate. You can rather use getDerivedStateFromProps also as mentioned in above answer.
Otherwise, you can use react hooks useState and useEffect to fix this. By passing props as dependency for useEffect, component will be rendered each time prop changes.
https://codesandbox.io/s/red-mountain-2zqbn?file=/src/App.js:202-206
https://github.com/uberVU/react-guide/issues/17
make sure the props has user.displayName, keep state.username == "" and try this:
static getDerivedStateFromProps(props,state){
if(props.user.displayName !== state.username){
state['username'] = props.user.displayName
return state;
}
return null;
}
In my react project, the componentWillReceiveProps() function seems to be called twice, but not sure what the problem is.
Here is the code.
import React, { Component, PropTypes } from 'react';
...
class MessagesList extends Component {
constructor(props) {
super(props);
this.state = {
message: '',
messages: []
};
...
componentWillMount() {
this.props.init_message();
};
componentWillReceiveProps(nextProps) {
this.setState({
messages: this.props.user.messages
});
var msgs = this.props.user.messages;
var total_group = [];
var msg_group = [];
var group_date = 0;
setTimeout(() => {
if (typeof msgs != 'undefined') {
for(var i = 0; i < msgs.length; i++) {
...
}
}
}, 100);
};
render() {
return (
<div className="row">
<div className="col-12">
<div className="messages">
{this.state.messages.map(message => {
return (
<div>{message.user}: {message.message} : {message.date}</div>
)
})}
</div>
</div>
...
</div>
);
}
}
I was going to read the msgs.length in the componentWillReceiveProps(), I got the following issue.
msgs.length is undefiend
After that I got the values of array, so I think the componentWillReceiveProps() seems to be called twice. So in the first call, can't read the value and then in the second call, read the value at least.
Please help me.
componentWillReceiveProps is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Note that if a parent component causes your component to re-render, this method will be called even if props have not changed. Make sure to compare the current and next values if you only want to handle changes.
You will get the details from react docs.
ComponentWillReceiveProps is a method from the growth/update phase of the React lifecycle.
The Growth phase is triggered in three different ways: changing of props, changing of state or calling forceUpdate().
The value you are referring to in componentWillReceiveProps, this.props.user.messages, is the current value not the nextProps value.
Also something to consider is that the setState method is actually an asynchronous function. So when that setting of state takes place, it will cause another rerender.
I suspect, but I cannot be sure without more of your code, that setState is called once with your original value from props which triggers another update cycle. During this next update cycle the setState method now sets state to the new prop values.
Are you perhaps meaning to use nextProps.user.messages instead of this.props.user.messages?
I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.
REDUX:
I have an action fetchItems, that gets all items from the database. This action works successfully.
REACT:
I have a container, UserProfile, that calls fetchItems in componentDidMount.
class UserProfile extends Component {
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.props.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.props.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?
Aseel
The cycle will be :
constructor()
componentWillMount() (will be soon deprecated by the way : https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
render() => first render (this.props.items, coming from mapStateToProps will be undefined)
componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.
So :
you should have two console.log('[Render]: Items: ', this.props.items);
you should deal with a "loading" state when the this.props.items is null
If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...
In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items
}
}
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentWillRecieveProps({ items }) {
this.setState({ items });
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.state.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.state.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps
Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().
componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.
There are certain ways you can resolve this.
The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null.
Always initialise your redux store with some meaningful data.
In your case if items will be array you can initialise with empty array.
When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.
You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.
Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
From Doc:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.
I am working with Reactjs. in which I am setting error if not properly entered.
Sample code is:
handleChange: function() {
if(shares) {
this.setState({
error_shares:''
})
}
if(this.state.error_shares === ''){
console.log('entered!!')
}
}
Here value of error_state doesn't reflect changed value in next if
Yes, that is the desired behavior.
The reason why the value has not been updated is because your component has not been re-rendered yet.
If you need such a feature, you should use the componentDidUpdate callback.
See the lifecycle methods here.
EXPLANATION:
Here is how React works:
Each component is a function of external props + internal state
If a change in either of the two is triggered, the component is queued to be re-rendered (it is asynchronous so that the client application is not blocked).
See the above link for exact sequence of operations but here is a little proof of concept.
import React from 'react';
class Example extends React.Component {
constructor(props, context) {
super(props, context);
// initial state
this.state = {
clicked: false
};
}
componentDidUpdate(prevProps, prevState) {
console.log(this.state.clicked); // this will show "true"
}
render() {
return (
<button
onClick={() => {
this.setState({clicked: true});
}}
>
</button>
);
}
}
setState is asynchronous in nature.
The second (optional) parameter is a callback function that will be
executed once setState is completed and the component is re-rendered.
so you can do something like this.
handleChange: function () {
if ( shares ) {
this.setState( {
error_shares: ''
}, function () {
if ( this.state.error_shares === '' ) {
console.log( 'entered!!' )
}
} )
}
}
source: https://facebook.github.io/react/docs/component-api.html#setstate
Yes, indeed.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
http://facebook.github.io/react/docs/component-api.html
No. You can't get update value in console just after setState. This is because as soon as setState is called, view is re-rendered. Hence to get updated value, you can check it inside render() .
render(){
if(this.state.error_shares === ''){
//your code.
}
}