ReactJS state change is inconsistent - javascript

When I have a button class called "DrawButton", that has this render
render() {
return(
<Button
onClick={this.props.toggleDraw.bind(this)}
style={{
backgroundColor: this.props.drawMode ? 'red' : 'blue'
}}
>
Draw
</Button>
);
}
And in parent App.js the state gets defined
state = {
drawMode: false
}
and there is a handler function
toggleDraw = (e) => {
console.log('App.js drawMode:' + this.state.drawMode);
this.setState({
drawMode: !this.state.drawMode
});
console.log('App.js drawMode:' + this.state.drawMode);
}
And finally the button:
render() {
return (
<div className="App">
<DrawButton
toggleDraw={this.toggleDraw}
drawMode={this.state.drawMode}
/>
</div>
);
}
The state doesn't get updated properly.
It outputs the following:
First click on the Button
App.js drawMode:false
App.js:27
App.js drawMode:false
App.js:31
Before the setState ran, the drawMode is false after setState ran, the drawMode is still false.
But the button still gets a red background.
Second click on the Button:
App.js drawMode:true
App.js:22
App.js drawMode:true
App.js:26
But the button is blue again despise drawMode in state is set to true.
Why is this inconsistency happening?

Firstly, your bind was used incorrectly, in your DrawButton onClick handler, just call this.props.toggleDraw.
This code : this.props.toggleDraw.bind(this) should be in the constructor of App.js file.
Secondly, do not use the console.log to check the value of state after setting, because the setState function runs asynchronously, use setState callback to check the value after setting:
toggleDraw = (e) => {
console.log('App.js drawMode:' + this.state.drawMode);
this.setState(
{ drawMode: !this.state.drawMode },
() => console.log('App.js drawMode:' + this.state.drawMode)
),
}

Related

multiple react state updates renders only the last update

I made a little function to add custom alerts to the state array, and the view renders them. The problem is, if i call the function twice in a row, the first alert is rendered only for a moment, and then its replaced with the second alert. When i call the method with a mouse click, the function works correctly.
I have tried to apply some waiting before pushing to the array list, but no luck with that.
const Element = () => {
const [alerts, setAlerts] = React.useState([])
const addAlert = (data) => {
setAlerts([...alerts, <CustomAlert key={alerts.length} message={data.message} color={data.color} />])
}
return (
<div>
<button onClick={() => {
// this renders only the last state update.
addAlert({message: "test", color: "error"});
addAlert({message: "2", color: "error"})
}
}>
add alert button
</button>
<div>
{alerts}
</div>
</div>
);
}
React updates the state asynchronously. This means when you are updating the state 2 times in a row, accessing the value of alerts directly might not have the latest inserted item. You should use a function instead when calling setAlerts:
const [alerts, setAlerts] = React.useState([]);
const addAlert = (data) => {
setAlerts((prevAlerts) => {
const newAlerts = [...prevAlerts];
newAlerts.push(
<CustomAlert
key={alerts.length}
message={data.message}
color={data.color}
/>
);
return newAlerts;
});
};
return (
<div>
<button
onClick={() => {
// this renders only the last state update.
addAlert({ message: "test", color: "error" });
addAlert({ message: "2", color: "error" });
}}
>
add alert button
</button>
</div>
);
alerts in your code refers to the value of the current render in both case so your addAlert won't work. To fix this, you can use the setter version with a function:
setAlerts(currentAlerts => [...currentAlters, <CustomAlert key={alerts.length} message={data.message} color={data.color} />])

How to re-render a Button to change "disabled" property on "renderMessageImage" with react-native-gifted-chat?

I am learning React Native and this is my first post at Stack Overflow.
I'm trying to re-render a button to make its disabled property change from false to true when onPress.
This button is being manually rendered insiderenderMessageImage from react-native-gifted-chat.
I am doing it through a boolean in this.state, however I see the state value updating on the logs but the button remains visually the same and it can be pressed again and again without actually changing its disabled property.
I have in my Chat.js class:
constructor(props) {
super(props);
this.state = {
isButtonDisabled: false
};
}
Then from Gifted Chat I call renderMessageImage to show a custom message design when a message has an image:
renderMessageImage = (props) => {
return (
<View>
<Button
title="Disable Button"
disabled={this.state.isButtonDisabled}
onPress={() => this.disableButton(props.currentMessage.id)}
/>
</View>)
}
This custom design is just a button that should call another method and then disable self:
disableButton = async (message_id) => {
console.log("Disabling message_id: " + message_id); //This shows the msg_id correctly where the button is
console.log(this.state.isButtonDisabled); //This shows false
this.setState(previousState => ({
isButtonDisabled: true
}));
console.log(this.state.isButtonDisabled); //This shows true (should update button disabled property)
return true;
}
For what I have tested so far, I can see that state value of isButtonDisabled is correctly changing from false to true, and as I have read, a change in the state should make the component re-render, but unfortunately it is not working that way.
More in-depth testing:
I then headed to GiftedChat.js sources from the react-native-gifted-chat to try debugging some of the code and see what is going on there.
What I found is that whenever I press my custom button, the componentDidUpdate of GiftedChat.js is being called twice:
componentDidUpdate(prevProps = {}) {
const { messages, text, inverted } = this.props;
console.log(this.props !== prevProps); //This is called twice per button click
if (this.props !== prevProps) {
//this.setMessages(messages || []); //I changed this line for the line below to directly update state
this.setState({ messages });
console.log(this.props !== prevProps); //This is called once per button click (first of the two calls)
}
if (inverted === false &&
messages &&
prevProps.messages &&
messages.length !== prevProps.messages.length) {
setTimeout(() => this.scrollToBottom(false), 200);
}
if (text !== prevProps.text) {
this.setTextFromProp(text);
}
}
Once all this checked, I see that the state is being updated and the GiftedChat.js component and messages are being updated once per button click, however my button in renderMessageImage is never re-rendering to properly show its new disabled value and actually become disabled.
I am totally clueless what else to test, so I would really appreciate some help on what am I doing wrong.
Thank you very much in advance.
Edit: renderMessageImage is called by GiftedChat on my Chat.js class render() section, then GiftedChat will call this.renderMessageImage whenever a message has an image (eg. this.state.messages[i].image != undefined). That part is working correctly, everything is being called as it should, just the re-render of the button status (disabled) is not updating:
render() {
return (
<View style={{ flex: 1 }}>
<GiftedChat
messages={this.state.messages}
onSend={this.sendMessage}
user={this.user}
placeholder={"Write a message..."}
renderMessageImage={this.renderMessageImage}
/>
<KeyboardAvoidingView behavior={'padding'} keyboardVerticalOffset={80}/>
</View>
);
}

React - change this.state onClick rendered with array.map()

I'm new to React and JavaScript.
I have a Menu component which renders an animation onClick and then redirects the app to another route, /coffee.
I would like to pass the value which was clicked (selected) to function this.gotoCoffee and update this.state.select, but I don't know how, since I am mapping all items in this.state.coffees in the same onClick event.
How do I do this and update this.state.select to the clicked value?
My code:
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
coffees:[],
select: '',
isLoading: false,
redirect: false
};
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map(c =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee()}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
export default withRouter(Menus);
I have tried passing the value like so:
gotoCoffee = (e) => {
this.setState({isLoading:true,select:e})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.state.select)
}
an like so:
<div onClick={(c) => this.gotoCoffee(c)}
or so:
<div onClick={(event => this.gotoCoffee(event.target.value}
but console.log(this.state.select) shows me 'undefined' for both tries.
It appears that I'm passing the Class with 'c'.
browser shows me precisely that on the uri at redirect:
http://localhost/coffee/[object%20Object]
Now if I pass mapped 'c' to {this.renderCoffee(c)}, which not an onClick event, I manage to pass the array items.
But I need to pass not the object, but the clicked value 'c' to this.gotoCoffee(c), and THEN update this.state.select.
How do I fix this?
You can pass index of element to gotoCoffee with closure in render. Then in gotoCoffee, just access that element as this.state.coffees[index].
gotoCoffee = (index) => {
this.setState({isLoading:true, select: this.state.coffees[index]})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map((c, index) =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee(index)}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
so based off your code you could do it a couple of ways.
onClick=(event) => this.gotoCoffee(event.target.value)
This looks like the approach you want.
onClick=() => this.gotoCoffee(c)
c would be related to your item in the array.
All the answers look alright and working for you and it's obvious you made a mistake by not passing the correct value in click handler. But since you're new in this era I thought it's better to change your implementation this way:
It's not necessary use constructor at all and you can declare a state property with initial values:
class Menus extends Component{
state= {
/* state properties */
};
}
When you declare functions in render method it always creates a new one each rendering which has some cost and is not optimized. It's better if you use currying:
handleClick = selected => () => { /* handle click */ }
render () {
// ...
coffees.map( coffee =>
// ...
<div onClick={ this.handleClick(coffee) }>
// ...
}
You can redirect with history.replace since you wrapped your component with withRouterand that's helpful here cause you redirecting on click and get rid of renderCoffee method:
handleClick = selected => () =>
this.setState(
{ isLoading: true},
() => setTimeout(
() => {
const { history } = this.props;
this.setState({ isLoading: false });
history.replace(`/${coffee}`);
}
, 5000)
);
Since Redirect replaces route and I think you want normal page change not replacing I suggest using history.push instead.
You've actually almost got it in your question. I'm betting the reason your state is undefined is due to the short lived nature of event. setState is an asynchronous action and does not always occur immediately. By passing the event off directly and allowing the function to proceed as normal, the event is released before state can be set. My advice would be to update your gotoCoffee function to this:
gotoCoffee = (e) => {
const selectedCoffee = e.target.value
this.setState({isLoading:true,select:selectedCoffee},() =>
{console.log(this.state.select})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
Note that I moved your console.log line to a callback function within setState so that it's not triggered until AFTER state has updated. Any time you are using a class component and need to do something immediately after updating state, use the callback function.

"Cannot update during an existing state transition" error in React

I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?
Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.
Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}
Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}
Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />
You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}

React, Local cached variable and firing onclick on render

I am trying to make a local variable for each component instance, something like a little cache because it is storing information that toggles something and does not need to be on the state/store. So I have attempted it like so:
Setting a default prop to keep my info in :
getDefaultProps: function getDefaultProps() {
return {
showPreviewModal: { value: false,
writable: true
}
};
},
Setting a function to toggle this prop :
togglePreviewModal: function togglePreviewModal() {
this.props.showPreviewModal = !this.props.showPreviewModal;
},
And having that function fired by a click function
<button className="btn btn-default btn-blue previewAsset" onClick={() => this.togglePreviewModal() }>
I thought this would work in theory, but the onclick is firing immedietly on render. I googled this issue and it seems like the best result is to change the click function to :
{() => { this.props.togglePreviewModal() }}
However this does not work either, the click function is still firing immediately.
You can't directly mutate props like that - since props are explicitly passed down from a parent component, you'd need to change the prop at the source. Presumably, it originates from the state of a component somewhere up the hierarchy.
To do that, you'd pass down a function along with the prop that changes it at the source (using setState()). Here's an example:
var ParentComponent = React.createClass({
togglePreviewModal: function() {
this.setState({
showPreviewModal: !this.state.showPreviewModal
};
},
getInitialState: function() {
return {
// Unnecessary but providing for clarity
showPreviewModal: false
};
},
render: function() {
// This is for whatever values you were mapping over
var childComponents = ...map(function() {
return <ChildComponent togglePreviewModal={this.togglePreviewModal} />;
});
if (this.state.showPreviewModal) {
return (<div>
<Modal />
{childComponents}
</div>);
} else {
return (<div>
{childComponents}
</div>);
}
}
});
var ChildComponent = React.createClass({
render: function() {
return <button
className="btn btn-default btn-blue previewAsset"
onClick={this.props.togglePreviewModal} />;
}
});
Note that I'm not invoking the function in the onClick of the <button>, just passing in the prop (which is a function).

Categories

Resources