Solution. So if you want to perform an action immediately after setting state on a state variable, we need to pass a callback function to the setState function. But in a functional component no such callback is allowed with useState hook. In that case we can use the useEffect hook to achieve it.
React components have a method available to them called setState Calling this. setState causes React to re-render your application and update the DOM. But, often there is a need to update our component's state using the current state of the component. Directly accessing this.
To update our state, we use this. setState() and pass in an object. This object will get merged with the current state. When the state has been updated, our component re-renders automatically.
The first thing React will do when setState is called is merged the object you passed into setState into the current state of the component. This will kick off a process called reconciliation. The end goal of reconciliation is to, in the most efficient way possible, update the UI based on this new state.
setState(updater[, callback])
is an async function:
https://facebook.github.io/react/docs/react-component.html#setstate
You can execute a function after setState is finishing using the second param callback
like:
this.setState({
someState: obj
}, () => {
this.afterSetStateFinished();
});
The same can be done with hooks in React functional component:
https://github.com/the-road-to-learn-react/use-state-with-callback#usage
Look at useStateWithCallbackLazy:
import { useStateWithCallbackLazy } from 'use-state-with-callback';
const [count, setCount] = useStateWithCallbackLazy(0);
setCount(count + 1, () => {
afterSetCountFinished();
});
render
will be called every time you setState
to re-render the component if there are changes. If you move your call to drawGrid
there rather than calling it in your update*
methods, you shouldn't have a problem.
If that doesn't work for you, there is also an overload of setState
that takes a callback as a second parameter. You should be able to take advantage of that as a last resort.
setState
return a Promise
In addition to passing a callback
to setState()
method, you can wrap it around an async
function and use the then()
method -- which in some cases might produce a cleaner code:
(async () => new Promise(resolve => this.setState({dummy: true}), resolve)()
.then(() => { console.log('state:', this.state) });
And here you can take this one more step ahead and make a reusable setState
function that in my opinion is better than the above version:
const promiseState = async state =>
new Promise(resolve => this.setState(state, resolve));
promiseState({...})
.then(() => promiseState({...})
.then(() => {
... // other code
return promiseState({...});
})
.then(() => {...});
This works fine in React 16.4, but I haven't tested it in earlier versions of React yet.
Also worth mentioning that keeping your callback code in componentDidUpdate
method is a better practice in most -- probably all, cases.
when new props or states being received (like you call setState
here), React will invoked some functions, which are called componentWillUpdate
and componentDidUpdate
in your case, just simply add a componentDidUpdate
function to call this.drawGrid()
here is working code in JS Bin
as I mentioned, in the code, componentDidUpdate
will be invoked after this.setState(...)
then componentDidUpdate
inside is going to call this.drawGrid()
read more about component Lifecycle in React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
With hooks in React 16.8 onward, it's easy to do this with useEffect
I've created a CodeSandbox to demonstrate this.
useEffect(() => {
// code to be run when state variables in
// dependency array changes
}, [stateVariables, thatShould, triggerChange])
Basically, useEffect
synchronises with state changes and this can be used to render the canvas
import React, { useState, useEffect, useRef } from "react";
import { Stage, Shape } from "@createjs/easeljs";
import "./styles.css";
export default function App() {
const [rows, setRows] = useState(10);
const [columns, setColumns] = useState(10);
let stage = useRef()
useEffect(() => {
stage.current = new Stage("canvas");
var rectangles = [];
var rectangle;
//Rows
for (var x = 0; x < rows; x++) {
// Columns
for (var y = 0; y < columns; y++) {
var color = "Green";
rectangle = new Shape();
rectangle.graphics.beginFill(color);
rectangle.graphics.drawRect(0, 0, 32, 44);
rectangle.x = y * 33;
rectangle.y = x * 45;
stage.current.addChild(rectangle);
var id = rectangle.x + "_" + rectangle.y;
rectangles[id] = rectangle;
}
}
stage.current.update();
}, [rows, columns]);
return (
<div>
<div className="canvas-wrapper">
<canvas id="canvas" width="400" height="300"></canvas>
<p>Rows: {rows}</p>
<p>Columns: {columns}</p>
</div>
<div className="array-form">
<form>
<label>Number of Rows</label>
<select
id="numRows"
value={rows}
onChange={(e) => setRows(e.target.value)}
>
{getOptions()}
</select>
<label>Number of Columns</label>
<select
id="numCols"
value={columns}
onChange={(e) => setColumns(e.target.value)}
>
{getOptions()}
</select>
</form>
</div>
</div>
);
}
const getOptions = () => {
const options = [1, 2, 5, 10, 12, 15, 20];
return (
<>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</>
);
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With