2

enter image description here

I'm working on a editor app using fabric.js.On localhost, When i click on Add Circle it works fine while on deployment it is causing cannot read properties of null.

Here is the code:

I'm using react context api by which i add objects in canvas and it displays on screen.

FabricCircle.js

import { fabric } from 'fabric';
import ContextCanvas from '../../../context/ContextCanvas';
import { Button } from '@chakra-ui/react';

const FabricTextBox = () => {
  const [canvas] = useContext(ContextCanvas);
  function addTextBox() {
    const textbox = new fabric.Textbox('Click on the Rectangle to move it.', {
      fontSize: 20,
      left: 50,
      top: 100,
      width: 200,
      fill: 'black',
      color: 'white',
      cornerColor: 'blue',

    });
    canvas.add(textbox);
    canvas.requestRenderAll();
  }
  return (
    <>
      <Button
        type="button"
        colorScheme="blue"
        onClick={addTextBox}
        variant={'ghost'}
        _hover={{}}
        _focus={{}}
        _active={{}}
        textColor={'white'}
        fontWeight={'light'}
      >
        Text Field
      </Button>
    </>
  );
};

export default FabricTextBox;

FabricCanvas.js

import React, { useContext, useLayoutEffect } from 'react';
import { fabric } from 'fabric';
import ContextCanvas from '../../context/ContextCanvas';


const FabricCanvas = () => {
  const [canvas, initCanvas] = useContext(ContextCanvas);


  useLayoutEffect(() => {

    return () => {
      initCanvas(new fabric.Canvas('c'));
    };
  }, []);


  return (
    <>
      <canvas
        id="c"
        width={window.innerWidth}
        height={window.innerHeight}
      />
    </>
  )
}
export default FabricCanvas;

ContextCanvas.js

import { fabric } from 'fabric';

const ContextCanvas = createContext();

export function CanvasProvider({ children }) {
  const [canvas, setCanvas] = useState(null);
  const initCanvas = c => {
    setCanvas(c);
    c.renderAll();
  };

  return (
    <ContextCanvas.Provider value={[canvas, initCanvas]}>
      {children}
    </ContextCanvas.Provider>
  );
}

export default ContextCanvas;
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
sohaib
  • 574
  • 5
  • 16

1 Answers1

2

I think the error is related to this line in FabricCircle.js

canvas.add(textbox);
      ^^^^

because your canvas object is null in production.


Assuming you use React.StrictMode

Using React.StrictMode

With Strict Mode starting in React 18, whenever a component mounts in development, React will simulate immediately unmounting and remounting the component:

Strict mode flow (read more)

* React mounts the component.
    * Layout effects are created.
    * Effect effects are created.
* React simulates effects being destroyed on a mounted component.
    * Layout effects are destroyed. [THIS]
    * Effects are destroyed.
* React simulates effects being re-created on a mounted component.
    * Layout effects are created
    * Effect setup code runs

The step marked with [THIS] is what makes you feel all right in the local environment (but it's an illusion... See why in the next section).


With that been said:

Your canvas is initialized null inside useState and in the useLayoutEffect, you are calling the initCanvas method inside the cleanup function (so it will only be called when it's too late in production, while in development with StrictMode act like an init-function although it's a cleanup function).

useLayoutEffect(() => {

    // Your code should be here

    return () => { // The returned function is the cleanup function

      // This is executed only when disposing the component.
      initCanvas(new fabric.Canvas('c'));
      // But when strict mode is active 
      //  the component is disposed and re-mounted immidiatly.
    };
}, []);

This is why the local environment works and the production environment doesn't.

Solution

Try updating your useLayoutEffect like this:

useLayoutEffect(() => {
   initCanvas(new fabric.Canvas('c'));
}, []);

Conclusion

You should not initialize your state inside a cleanup function.

In this case, React.StrictMode behavior prevents you from realizing the error (without strict mode, it wouldn't even work in development).

Since you were initializing the canvas inside the cleanup function, the canvas never get initialized in time (without the strict mode), remaining null, as the error you receive states.

Doc
  • 655
  • 4
  • 11
  • 1
    Yes it works. I removed the strict mode and return from useLayoutEffect – sohaib Jul 19 '22 at 11:20
  • 1
    Great! Once the `useLayoutEffect` is fixed you shouldn't have any problems with StrictMode anyway :) Of course if you don't care you can remove it, but it's not mandatory. – Doc Jul 19 '22 at 11:33