Can I execute a function after setState is finished updating?

前端 未结 5 1721
温柔的废话
温柔的废话 2020-11-30 17:32

I am very new to ReactJS (as in, just started today). I don\'t quite understand how setState works. I am combining React and Easel JS to draw a grid based on

相关标签:
5条回答
  • 2020-11-30 17:59

    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

    0 讨论(0)
  • 2020-11-30 18:01

    Making 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.

    0 讨论(0)
  • 2020-11-30 18:08

    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>
          ))}
        </>
      );
    };
    
    0 讨论(0)
  • 2020-11-30 18:21

    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.

    0 讨论(0)
  • 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();
    });
    
    0 讨论(0)
提交回复
热议问题