Why doesn't react update the style in the setState? The color of the text does not update to green with the setState function, it remains blue.
class Practice extends Component {
state = {
count: 0,
color: "blue"
}
style = {
color: this.state.color
}
handleIncrement = () => {
this.setState({ count: this.state.count});
this.setState({ color: "green"});
}
render() {
return(
<div>
<h1 style={this.style}>
The color in this state is {this.state.color}.
The number in count is {this.state.count}.
<button
onClick={this.handleIncrement}
>
Increment
</button>
</h1>
</div>
);
}
}
Information on how a component should render should flow from state alone - this will allow you to call setState to change how the component renders. Putting a .style property on the component instance itself won't work - put it into the state instead.
Rather than duplicating the color in different parts of the state, put it in just one place, in the style object in state.
Not 100% certain, but you also probably want
this.setState({ count: this.state.count});
to be
this.setState({ count: this.state.count + 1 });
class Practice extends React.Component {
state = {
count: 0,
style: {
color: 'blue',
}
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ style: {
...this.state.style,
color: "green"
}});
}
render() {
return(
<div>
<h1 style={this.state.style}>
The color in this state is {this.state.style.color}.
The number in count is {this.state.count}.
<button
onClick={this.handleIncrement}
>
Increment
</button>
</h1>
</div>
);
}
}
ReactDOM.render(<Practice />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
While other answers explain well why your code is not working in terms of React state, there is one thing I noticed that might be another point of confusion.
When you're assigning a value to your style property like this:
style = {
color: this.state.color
}
you might be thinking that you're assigning style.color a "reference" to the string that this.state.color points to. What actually happens is that you're assigning the value "blue", because the string is a primitive type in JS. So after you've done that, your style object is really just
{
color: "blue"
}
So even if you somehow change the value of this.state.color, this fundamentally wouldn't result in updating the value of this.style.color.
state in react app will only update using setState if and only if you have inclued the state in this.state if you are using constructor or state property without constructor.
Live Demo
You should create state as:
state = {
count: 0,
style: {
color: "blue"
}
};
and update the state as:
handleIncrement = () => {
this.setState((oldState) => {
return {
count: oldState.count + 1,
style: {
...oldState.style,
color: "green"
}
};
});
};
Related
I have a functional react component with a style defined as below:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => {
console.log('styledInnerItem color: ', props.color);
return 'red';
}
}
}))(InnerItem);
Basically I am trying to pass the props.color as the background color, however it's not even returning red which is set temporarily as the bg Color.
When I log the props, it is returning the color from the first render, and it is adding in the html file. but the styles only get added when I click on my component and the colors get applied to the component.
It works fine when the background Color is not a function and I need it to work the same by reading the color from the props.
The issue might be because the withStyles HOC only updates the styles when the component re-renders, but the props passed to the component do not trigger a re-render. One way to solve this is to pass the color value to the component as a state and update the state when the props change, which will trigger a re-render and the styles will get updated.
Here's how you can implement this:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => props.color
}
}))(InnerItem);
class InnerItemWrapper extends React.Component {
state = {
color: 'red'
};
componentDidUpdate(prevProps) {
if (this.props.color !== prevProps.color) {
this.setState({ color: this.props.color });
}
}
render() {
return <StyledInnerItem color={this.state.color} />;
}
}
In this way, when the props.color changes, the component re-renders and the styles get updated accordingly.
Some Background:
Currently I'm trying to implement a TouchableOpacity component that represents a switch, which has a state of either 'on' or 'off'. The component works as follows:
On load, the component reads using AsyncStorage using the current date as the key (if an entry exists for the key, then the component has the state 'on' (true), if not 'off' (false)
The background color of the child View component is related to the state ('on': backgroundColor is green; 'off': backgroundColor is 'none')
If the component is pressed, the component is disabled while waiting for the AsyncStorage method to complete.
If the component is pressed while the state is 'off', a new entry is written, and on completion, the state is set to 'on'.
If the component is pressed while the state is 'on', the entry with the given key is deleted, and the state is set to 'off'.
Current Code:
class Switch extends Component {
constructor(props) {
super(props);
this.state = {
switchStatus: null,
awaitingIO: false
};
}
componentDidMount() {
switchStatusOfDate(new Date()).then((response) => {
this.setState({ switchStatus: response != null ? true : false });
}).catch((error) => {
// error
});
}
render() {
const {switchStatus, awaitingIO } = this.state;
if (switchStatus == null) {
return <View style={[styles.circleButton]}>
<Text>Loading</Text>
</View>
}
const style = {
backgroundColor: switchStatus ? 'green' : 'none',
...styles.circleButton
};
return (<TouchableOpacity disabled={awaitingIO} onPress={this._updateSwitchStatus.bind(this)} >
<View style={style}>
{/* Some children */}
</View>
</TouchableOpacity>);
}
async _updateSwitchStatus() {
this.setState({ awaitingIO: true });
if (this.state.switchStatus) {
await unsetSwitchStatusForDate(new Date());
} else {
await setSwitchStatusForDate(new Date());
}
this.setState({
switchStatus: !this.state.switchStatus,
awaitingIO: false,
});
}
}
const styles = StyleSheet.create({
circleButton: {
flex: 0,
alignItems: 'center',
justifyContent: 'center',
width: 150,
height: 150,
borderWidth: 2,
borderRadius: 75,
borderColor: 'green'
},
});
export default Switch;
Storage Methods:
function getDateKey(date) {
let year = date.getFullYear();
let month = date.getMonth();
let day = date.getDate();
return `#${year}${month}${day}`;
}
export async function switchStatusOfDate(date) {
try {
return await AsyncStorage.getItem(getDateKey(date));
} catch (e) {
// error
}
}
export async function setSwitchStatusForDate(date) {
try {
let value = `${date.getHours()}:${date.getMinutes()}`;
return await AsyncStorage.setItem(getDateKey(date), value);
} catch (e) {
// error
}
}
export async function unsetSwitchStatusForDate(date) {
try {
await AsyncStorage.removeItem(getDateKey(date));
} catch (e) {
// error
}
}
The Problem:
Currently, on load, the background color is set correctly (if the component is pressed to set the state to 'on', and an entry is written, upon reloading the application, the component background color is green). Also, when the state is loaded as 'off' (no entry exists), and I press the component, the background color changes from 'none' to green correctly. However, all further presses have no impact on the background color.
The state is being set correctly, and the AsyncStorage methods are also working. I've tried logging the switchStatus and style in the render() method, and the values returned are correct, but it doesn't have any effect on the actual render (it stays green no matter).
Does anyone know why this might be?
I feel like there's an obvious solution to this that I'm just missing, so any help would be appreciated! Thank you in advance.
Edit 1: I did alter some variable names so the code is self-contained to the question, but nothing else was changed (just in case there's any errors that are spotted caused by it).
Edit/Update 2: I've noticed that when using the element inspector with expo on my phone, the css property backgroundColor: 'none' is applied to the view, but it's background in the render is still green. This isn't due to any children components either, as I've removed them and it's still the case.
I've found that using,
backgroundColor: 'rgba(0, 0, 0, 0)'
or
backgroundColor: 'transparent'
where the last value in the first one is the opacity (in this case, completely transparent), works.
My problem was that I thought 'none' was a valid value for backgroundColor in css. This was not the case, whoops!
I am doing a random quote app and it seems like I am using react state wrong when I try to change the body background and text-box text color. The body background color is one step behind all the time and I want them to change to the same color simultaneously.
Any idea what is wrong with my code? I just added here the class component that is doing the business.
P.S. I hope my code is not too hard to read, I am a beginner
class Quote extends React.Component {
constructor() {
super();
this.state = {
quote: [],
author: [],
apiData:[],
color: ""
};
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
//Here I made an API call for this.state.apiData to get my quotes
}
handleClick(){
var randomColor = '#'+Math.floor(Math.random()*16777215).toString(16); //Here I get a random color
function getRandNum(min, max){
return Math.floor(Math.random()*(max-min)+min) //Here I get a random number in the range of my API array length
}
let randomNum = getRandNum(0,this.state.apiData.length)
this.setState({
quote: this.state.apiData[randomNum].text,
author: this.state.apiData[randomNum].author
color: randomColor
})
document.body.style.backgroundColor = this.state.color;
console.log(this.state.color)
}
render() {
return (
<div class="quote-box">
<h1 style={{color: this.state.color}}> {this.state.quote}-"{this.state.author}" </h1>
<button
onClick={this.handleClick}
class="change-button"
style={{color: "white", backgroundColor: this.state.color}}
>Change
</button>
</div>
);
}
}
Because setState sets the states asynchronously (the background color of the body is updated before the color is updated in the states). You can pass a second parameter to setState method that will be called after the states are updated.
this.setState(
{
quote: this.state.apiData[randomNum].text,
author: this.state.apiData[randomNum].author
color: randomColor
},
() => document.body.style.backgroundColor = this.state.color
)
Or you can alternatively set the background color of the body with randomColor variable.
this.setState({
quote: this.state.apiData[randomNum].text,
author: this.state.apiData[randomNum].author
color: randomColor
})
document.body.style.backgroundColor = randomColor;
Or you can use componentDidUpdate to detect the changes in states.
I have a div, with player score, deaths and assists:
<div className="liveMatchPlayerScore">
{data.kill_count}/{data.death_count}/{data.assists_count}
</div>
Every time the kill or death count changes, I'd like the text to turn a bold white for 3 seconds.
I was thinking of using react-spring for this, specifically useTransitions, but the documentation shows examples using an array of items. I going to put each of the scores in an array, but it seems counterproductive.
Previously i tried wrapping the scores in an "Spring" component from react-spring but that only animated the scores on their initial render - not when they update.
How can I make the kill_count and death_count become white for 3 seconds upon changing value?
Thank you
I used #PeterAmbruzs solution, but i seem to be getting strange numbers. For example in the images below, first the score was 0/0/0 and the first number increased by 1. Instead of becoming 1/0/0, it became 01/0/0. I'm also getting absurdly high numbers for some reason. Does anyone know why this might be happening?
I have also a solution. I think it is quite simple. First you create a component for the animated numbers. I wrapped it in react.memo to update it only when its property change. You can see it is bold, and red at start, but after 3sec it became normal and black. But you can change the style whatever you want. I added skip property to prevent animation for example for the first render.
const FadeNumber = React.memo(({ value, skip }) => {
const style = useSpring({
from: { color: "red", fontWeight: "bold" },
to: { color: "black", fontWeight: "normal" },
delay: 3000
});
return (
<animated.span style={{ margin: "10px", ...(skip ? {} : style) }}>
{value}
</animated.span>
);
});
Now there is a trick to reRender the animation at property change. Simly put the value to the key. When the key changes a new component will be created, so the animation will be played again. I added a unique prefix to prevent side effects.
<FadeNumber skip={kill === 0} key={"a" + kill} value={kill} />
And the whole example:
https://codesandbox.io/s/react-spring-change-fade-out-j8ebk
https://stackblitz.com/edit/react-5sztov?file=index.js
setTimeout() is still a viable construct - even when working in React.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
assistsCount: 0,
color: 'black',
deathCount: 0,
fontWeight: 'normal',
killCount: 0,
setStyleToReset: false,
};
}
increaseKillCount () {
this.setState((prevState, props) => {
return {
killCount: prevState.killCount + 1,
color: 'white',
fontWeight: 'bold',
setStyleToReset: true,
};
});
}
render() {
if (this.state.setStyleToReset) {
this.resetStyle();
}
return (
<div>
<div style={{
backgroundColor:'green',
}}>
<span style={{color:this.state.color, fontWeight:this.state.fontWeight}}>{this.state.killCount}</span>/{this.state.deathCount}/{this.state.assistsCount}
</div>
<button onClick={() => this.increaseKillCount()}>Increase Kill Count</button>
</div>
);
}
resetStyle() {
setTimeout(() => {
this.setState({
color: 'black',
fontWeight: 'normal',
});
}, 3000);
}
}
render(<App />, document.getElementById('root'));
I want to use backgroundColor of style1 as a state, and change it in the function change().
How can I access style1?
My point is to call the function change from another function, making the button change its color to yellow, then change it's colour to blue again after sometime.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
//style1.backgroundColour: blue //? Can't
}
this.change=this.change.bind(this)
}
change() {
this.setState({ style1.backgroundColour: yellow }) //?
}
render(){
return (
<View style={styles.style1} > </View>
);
}
}
const styles = StyleSheet.create({
style1:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
backgroundColor:'blue',
},
});
Update: This question and my answer was based on class components. But functional components and hooks are the current way of using React for a long time now.
First you should create a state for your backgroundColor:
const [backgroundColor, setBackgroundColor] = useState('blue');
then change it's value in your function
setBackgroundColor('yellow');
and finally, use it in style prop:
style={[styles.style1, {backgroundColor}}
Old answer, using class components
You can give an array to style prop. So you can put any other styles from another sources.
First you should declare a default value for your backgroundColor state:
this.state = {
backgroundColor: 'blue',
};
then set it's state in your function as normal string state:
this.setState({backgroundColor: 'yellow'});
and finally, use it in style prop:
style={[styles.style1, {backgroundColor: this.state.backgroundColor}]}