I'm still learning JS/React, so it's likely I'm doing this completely wrong. Any criticism is welcome.
I have a Canvas with a drawning on it. I want to change the color of the drawning multiple times when a button is pressed. To be clear: I want a single click on the button to change the color of the drawning multiple times.
I've tried doing this a few different ways, but they are mosly variations of two:
When the button is pressed, it calls the method that will change the state multiple times, but React only bothers to render the last state I set. (Which makes sense)
Using setTimeout
for each setState
, but it seems it breaks the method, and the render never changes.
Here is a sample code:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
color: "#000000",
}
this.changeColors = this.changeColors.bind(this);
}
changeColors() {
let colors = ["#000000", "#0000FF", "#FF0000", "#00FF00"];
for (let nextColor in colors) {
console.log(`Color now ${colors[nextColor]}`);
// This seems to break it
//setTimeout(function(){ this.setState({color: colors[nextColor]}); }, 3000);
// This only renders last state
this.setState({color: colors[nextColor]});
}
}
render() {
return (
<div className="App">
<h1>Change Colors</h1>
<MyButton changeColor={this.changeColors}/>
<MyCanvas color={this.state}/>
</div>
);
}
}
class MyButton extends React.Component {
render() {
return (
<button
type="button"
className="btn btn-secondary"
onClick={() => this.props.changeColor()}>
Color
</button>
);
}
}
class MyCanvas extends React.Component {
componentDidMount() {
this.drawOnCanvas(this.props.color)
}
componentDidUpdate() {
this.drawOnCanvas(this.props.color)
}
drawOnCanvas(color) {
const ctx = this.refs.canvas.getContext('2d');
ctx.clearRect(0, 0, 300, 300)
ctx.fillStyle=color.color;
ctx.fillRect(10, 10, 100, 100);
}
render() {
return (
<canvas id="canvas" ref="canvas" width={300} height={300}/>
);
}
}
export default App;
What I'm doing wrong and how can I achieve the multiple color changes with react?
Without setTimeout
all the renders will be basically merged into one, this is how React works. However you could try setTimeout
with a dynamic timeout.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
color: "#000000",
}
}
changeColors = () => {
let colors = ["#000000", "#0000FF", "#FF0000", "#00FF00"];
colors.forEach((color, i) => {
setTimeout(() => {
this.setState({ color });
}, 500 * i);
});
}
render() {
return (
<div className="App" style={{ color: this.state.color }}>
<h1>Change Colors</h1>
<button onClick={this.changeColors}>change</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>