Using Fabric.js with React

In this post, you will learn how to use fabric.js inside react together with context API.

Using Fabric.js with React

I was recently very interested in building a canvas application where I could add text, images, draw, and finally export everything to a png image format.
You can view the app demo in the video embedded below.

While researching the best library for working with canvas, I discovered fabric.js
To use fabric in our react project, we have to initialize it.

We will use useLayoutEffect to help us in our quest.

export default function CanvasApp() {
    useLayoutEffect(() => {

		const canvas = new fabric.Canvas('canvas', {
                height: 600,
                width: 800,
                fireRightClick: true,
                fireMiddleClick: true,
                stopContextMenu: true,
                backgroundColor: undefined,
                backgroundImage: undefined,
		});
        canvas.requestRenderAll()
    }, [])
    return (
        <Box sx={{ position: "relative" }} height={"100vh"} width={"100%"}>
            <canvas id="canvas" />
        </Box>
    );
}

Canvas now holds an instance of fabric Canvas.
Next, we might want to use fabric canvas in different parts of our application and that's where context API comes in.

Let's create a context to store our fabric canvas instance into.

import { createContext } from 'react';
import { fabric } from 'fabric';

type CanvasContext = {
  canvas: fabric.Canvas | undefined
  setCanvas: (canvas:fabric.Canvas) => void
};

export default createContext<CanvasContext>({
  canvas: undefined,
  setCanvas: () => {},
});

We defined our canvas property where the instance will be stored, and we defined our setter which will set the value of canvas.

Next, we need to use the context in our root component that wraps all of the children that may use the fabric canvas instance.

function App() {
  const [canvasVal, setCanvasVal] = useState<fabric.Canvas>();
  const setCanvas = (canv: fabric.Canvas) => {
    setCanvasVal(canv);
  };

  return (
    <CanvasCTX.Provider value={{
      canvas: canvasVal,
      setCanvas,
    }}
    >
      <Grid container spacing={2}>
        <Grid item xs={1}>
          <CanvasControls />

        </Grid>
        <Grid item xs={11}>
          <CanvasApp />

        </Grid>
      </Grid>
      <ContextMenu />
      <Box sx={{
        backgroundColor: 'transparent', position: 'absolute', bottom: (theme) => theme.spacing(2), left: (theme) => theme.spacing(2),
      }}
      >
        Developed by Ray Luxembourg
      </Box>
    </CanvasCTX.Provider>
  );
}

Here we create the set function and the state to pass the context provider its value.

Next, we will go back to our CanvasApp and set the context value with our setter.

export default function CanvasApp() {
	const { setCanvas } = useContext(CanvasCTX);
    useLayoutEffect(() => {

		const canvas = new fabric.Canvas('canvas', {
                height: 600,
                width: 800,
                fireRightClick: true,
                fireMiddleClick: true,
                stopContextMenu: true,
                backgroundColor: undefined,
                backgroundImage: undefined,
		});
        canvas.requestRenderAll()
		setCanvas(canvas)
    }, [])
    return (
        <Box sx={{ position: "relative" }} height={"100vh"} width={"100%"}>
            <canvas id="canvas" />
        </Box>
    );
}

Now that we have set the canvas value in our context, we can access the instance from anywhere in our nested components under the provider.

function MyComponents() {
	const { canvas } = useContext(CanvasCTX);
    if (canvas) {
    	console.log(canvas.getActiveObject())
    }
    
    return <div>I am a child component</div>
    
}

Hope this post has helped you.
Best regards :).