asotog / fabricjs-react

support fabricjs from react
122 stars 45 forks source link

Is it possible to use this in a hook? #44

Open subtext916 opened 3 months ago

subtext916 commented 3 months ago

Hello, I have been exploring this solution and wanted to wrap it in my own hook to eliminate the "fabricjs" specific stuff and create a more generic "useCanvas" hook which returns a Canvas (instance of the FabricJSCanvas) and some methods to work with it, which all use the "editor" returned from the fabricjs-react hook. I am finding that this approach does not work because if I make a change to my App.tsx parent component, which triggers a re-render (hot deploy), the whole thing crashes with the error: TypeError: Cannot read properties of null (reading 'clearRect')

I have tried adding a useEffect return function to call editor.canvas.dispose() editor.canvas.off() editor.canvas.clear() and I cannot find a way around this problem. Is this a problem? Am I using this hook wrong? Any advice would be appreciated.

subtext916 commented 3 months ago

To give an example of what I am trying to do: A simplified example of my hook:

const useCanvas = ({ className = 'canvas' }) => {
const [initialized, setInitialized] = useState(false)
   const { selectedObjects, editor, onReady: onReadyOriginal } = useFabricJSEditor()
   const getCanvasObjects = useCallback(() => {
      return editor?.canvas?.getObjects()
   }, [editor])

   useEffect(() => { 
      if (editor?.canvas && !initialized) setInitialized(true)
   }, [initialized, editor, onReady])

   const Canvas = useMemo(() => {
    return () => <FabricJSCanvas className={className} onReady={onReady} />;
  }, [className, onReady]);

  return {
    Canvas,
    ready: initialized,
    getCanvasObjects
  }

}
export default useCanvas
asotog commented 3 months ago

@subtext916 hi can you please share code when is failing on clearRect

subtext916 commented 3 months ago

Exact details... React project with the following files: tsconfig.json

index.tsx:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
    <App />
);

App.tsx:

import React from 'react'
import useCanvas from './useCanvas'

function App() {
  const { Canvas } = useCanvas({
    className: 'canvas'
  })

  return (
    <div className="App">
      <Canvas />
    </div>
  )
}

export default App

useCanvas.tsx:

import React, { useEffect, useMemo, useCallback, useState, useRef } from 'react'
import { FabricJSCanvas, useFabricJSEditor } from 'fabricjs-react'
import { fabric } from 'fabric'

interface CanvasProps {
  className?: string
}

const useCanvas = ({ className = 'canvas' }: CanvasProps) => {
  const editorRef = useRef<any>(null)
  const [initialized, setInitialized] = useState(false)
  const { editor, onReady: onReadyOriginal } = useFabricJSEditor()

  const onReady = useCallback(onReadyOriginal, []) // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
      if (editor?.canvas) {
        if (!initialized) {
          setInitialized(true)
          editorRef.current = editor
        }
      }
  }, [initialized, editor, onReady])

  /**
   * Main canvas component
   */
  const Canvas = useMemo(() => {
    return () => <FabricJSCanvas className={className} onReady={onReady} />;
  }, [className, onReady]);

  useEffect(() => {
    return () => {
      editorRef.current?.canvas.off()
      editorRef.current?.canvas.clear()
      editorRef.current?.canvas.dispose()
      fabric.util.requestAnimFrame(() => {})
    }
  }, [])

  return {
    Canvas,
    ready: initialized
  }
}

export default useCanvas

Now, run react project... npm run start

Modify App.tsx or index.tsx, it crashes with the clearRect error.

Am I doing something wrong? Thank you for looking at this

subtext916 commented 3 months ago

Also, could it be related to this topic? https://github.com/fabricjs/fabric.js/issues/8299 (scroll down to the "React Compatibility" section)

subtext916 commented 3 months ago

Oh, one other detail. I am using: "fabric": "^5.3.0", "fabricjs-react": "^1.2.2",

asotog commented 3 months ago

not sure, can you prepare a codesanbox runtime, I think based on the sample code, there is no benefit for now to separate hook from canvas component, all together can be inside Canvas component (don't think returning component from hook looks like a good pattern)