javascriptreactjspixi.jsreact-ref

Invalid value of 0 passed to checkMaxIfStatementsInShader when trying to use a canvas via React useRef hook


I have created a simple React app, which is using PixiJS and it works well.

The 1:1 aspect ratio of the canvas there is maintained via a ResizeObserver regardless of the browser window width, as you can see here:

animated screenshot

However, I have to use a workaround there: I let the PIXI.Application to create a canvas element dynamically and then assign it to the React childRef.current as shown below:

const PixiGame = () => {
  const parentRef = useRef(null);
  const childRef = useRef(null);  // as a workaround I have to assign the canvas element below, created by PIXI app

  useEffect(() => {

    const parentElement = parentRef.current;

    const app = new Application({
      backgroundColor: 0xccffcc,
      width: 800,
      height: 800,
    });

    // the PIXI app has created a canvas element - make it a child of the parent div
    const childElement = app.view;
    childElement.id = "child";
    childElement.classList.add("child");
    childRef.current = childElement;
    parentElement.appendChild(childElement);

When I try to go a more natural way and declare the canvas element in JSX of the otherwise similar app, then I get the error message:

Error: Invalid value of `0` passed to `checkMaxIfStatementsInShader`
    at checkMaxIfStatementsInShader (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:5189:11)
    at _BatchRenderer2.contextChange (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:8129:27)
    at Runner.emit (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:5340:22)
    at ContextSystem.initFromContext (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:8619:155)
    at ContextSystem.initFromOptions (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:8631:10)
    at ContextSystem.init (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:8603:168)
    at Runner.emit (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:5340:22)
    at StartupSystem.run (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:11443:27)
    at new _Renderer2 (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:13203:478)
    at autoDetectRenderer (https://masterdetailpixidynamic-hn0o--5173--41fbae16.local-corp.webcontainer.io/node_modules/.vite/deps/pixi__js-legacy.js?v=b911e8ad:12931:14)

Here is the code I am trying to use, with the canvas element defined in JSX:

const PixiGame = () => {
  const parentRef = useRef(null);
  const childRef = useRef(null);

  useEffect(() => {
    const parentElement = parentRef.current;
    const childElement = childRef.current;  // here I just use the existing canvas element, defined in JSX below

    const app = new Application({
      backgroundColor: 0xccffcc,
      width: 800,
      height: 800,
      view: childElement,
    });

    const resizeObserver = new ResizeObserver((entries) => {
      for (let entry of entries) {
        const { width, height } = entry.contentRect;
        const minDimension = Math.floor(Math.min(width, height));

        // maintain the 1:1 aspect ratio of the child element
        childElement.style.width = `${minDimension}px`;
        childElement.style.height = `${minDimension}px`;
      }
    });

    resizeObserver.observe(parentElement);

    const background = new Graphics();
    for (let i = 0; i < 8; i++) {
      for (let j = 0; j < 8; j++) {
        if ((i + j) % 2 == 0) {
          background.beginFill(0xccccff);
          background.drawRect(i * 100, j * 100, 100, 100);
          background.endFill();
        }
      }
    }
    app.stage.addChild(background);

    const texture = Texture.from("https://pixijs.com/assets/bunny.png");
    const bunny = new Sprite(texture);
    bunny.anchor.set(0.5);
    bunny.x = 50;
    bunny.y = 50;
    bunny.width = 100;
    bunny.height = 100;
    app.stage.addChild(bunny);

    return () => {
      resizeObserver.unobserve(parentElement);
      resizeObserver.disconnect();
      parentElement.removeChild(childElement);
      app.destroy(true, true);
    };
  }, []);

  return (
    <div className="verticalFlexContainer">
      <div className="hint">Game No. 1 Score1:Score2</div>
      <div className="parent" ref={parentRef}>
        <canvas className="child" id="child" ref={childRef}></canvas>
      </div>
      <div className="status">A game hint to do this and that...</div>
    </div>
  );
};

The error seems to be somehow related to React lifecycle or maybe its strict mode...

I tried to print the childRef.current and parentRef.current and they are never null.

Does anybody please have a suggestion on how to have the canvas be defined in JSX part of my app and still being able to use it in the Pixi code?

PS: Yes, I have tried the CSS aspect-ratio: 1 and it was not working well for me, that is why I use ResizeObserver.

PPS: No, I would prefer not to use the "@pixi/react" package, because I would like to use PixiJS in imperative (and not in declarative) style and it seems to work well enough.


Solution

  • Had no much time to play with it, but it may help.

    App.destroy() function is described to remove canvas element from the DOM, so when your useEffect run cleanup it destroys canvas element from JSX.

    in first example you create new canvas element on each useEffect run, but in second code snippet it created once in JSX -> got deleted on unmound -> and no recreated ever resulting in an error.