简体   繁体   中英

useRef.current exists, but is null depending on scope

I am trying to draw an image on a canvas using React and Fabric.js.
Demo(CodeSandBox)

In the above demo, when the "Draw image" button is pressed, one would expect an image to be immediately drawn on the canvas, but no image is drawn on the canvas.

参考当前空

The reason why the image is not drawn seems to be that the current of useRef is null when executing the function to draw the image on the canvas.(If the isInput condition on line 50 is removed, containerRef.current is no longer null and the image can be drawn on the canvas.)

Could you help me to draw an image on the canvas?


The code is as follows.

const App = () => {
  const [img, setImg] = useState(defaultImg);
  const changeImg = ({ target: { value } }: { target: { value: string } }) =>
    setImg(value);

  const [isInput, setIsInput] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  const checkCurrent = (caller: string) => {
    console.log(`[${caller}]Container: ${containerRef.current}`);
  };
  useEffect(() => {
    if (!isInput) return;

    checkCurrent("useEffect");
  }, [isInput]);

  const drawImg = (e: { preventDefault: () => void }) => {
    e.preventDefault();
    if (!img) return;
    setIsInput(true);

    checkCurrent("drawImg");

    canvas = new fabric.Canvas("canvas");
    fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
      canvas.add(imgObj).renderAll();
    });
  };

  return (
    <div className="App">
      {!isInput && (
        <form onSubmit={drawImg}>
          Demo image url
          <input type="text" onChange={changeImg} defaultValue={img} />
          <button type="submit">Draw image</button>
        </form>
      )}

      {isInput && (
        <>
          <button onClick={() => setIsInput(false)}>Reset</button>
          <div ref={containerRef} className="canvas_container">
            <canvas ref={canvasRef} id="canvas" />
          </div>
        </>
      )}
    </div>
  );
};

export default App;

I understand that containerRef.current is null if there is no DOM.

However,

  1. the div to which containerRef is set is drawn by setIsInput(true)
  2. containerRef.current obtained inside useEffect is not null.

From the above points, we can expect that containerRef.current is not null even inside the drawImg function.

Calling setIsInput(true) just requests that react rerender the component. This will happen soon, but it generally not happen immediately and synchronously. So your code on the line after setIsInput(true) cannot assume that the ref has been updated yet.

So typically, you would do this in two steps. First, rerender the component, second draw the image to the canvas. This can be done by putting the drawing code into a useEffect since, as you've noticed, this happens after the ref is populated (because it happens after the render has been flushed to the dom):

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;
  setIsInput(true);
}

useEffect(() => {
  if (!isInput) return;

  const canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
}, [isInput]);

Since you're using react 18 there is one additional option to mention, though i recommend that you don't use this unless you really need it. React 18 adds a new function called flushSync which can force state updates to be rendered synchronously.

import { flushSync } from 'react-dom'; // Note: 'react-dom', not 'react'

// ...

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;

  flushSync(() => {
    setIsInput(true);
  });

  canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
};

Make sure to check out the notes in the documentation for flush sync , because there are consequences if you choose to use it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM