Search code examples
javascriptreactjsfabricjs

Fabric js edit text added in canvas via input


I am using fabricjs and fabricjs-react in react app. I need the text to be edited via input. But it doesn't work. Maybe I'm missing something? But when I enter text into the input, the canvas is not updated and the text remains the same. What am I doing wrong? How to access existing text in fabricjs?

There is my code https://codesandbox.io/s/sleepy-stitch-yvcr22

UPD: I tried to do https://codesandbox.io/s/clever-christian-zxb5ge with the addition of ID. It works. Maybe there is some more universal solution?

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


export default function App() {
  const { editor, onReady } = useFabricJSEditor()

  const [text, setText] = useState('')
  let headText = new fabric.Text("test", {
    fill: 'black',
    top: 50,
    left: 50,
  })

  const _onReady = (canvas) => {
    canvas.backgroundColor = 'yellow'
    canvas.setDimensions({
      width: 200,
      height: 200,
    })
    canvas.add(headText);
    canvas.renderAll()
    onReady(canvas)
  }

  useEffect(() => {
    if (editor) {
      headText.text = text
      editor.canvas.renderAll()
    }
  }, [text])

  return (
    <>
        <input onChange={(e) => setText(e.target.value)} />
        <FabricJSCanvas onReady={_onReady} />
    </>
  )
}

Solution

  • The reason why it doesn't work is because whenever the setText() state is called, the App() function is called again in order to render the state changes.

    Here lies the problem with this; any subsequence call to App() you end up creating a new instance of headText

    let headText = new fabric.Text("test", {
        fill: 'black',
        top: 50,
        left: 50,
    })
    

    Your useEffect is using that new instance whereas the canvas contains the very first instance. So, any changes you attempt is referring to the wrong object.

    This quick dirty check proves this is a different object:

    useEffect(() => {
      if (editor) {
        editor.canvas.getObjects()[0].text = text;
        editor.canvas.renderAll()
    
        console.log(editor.canvas.getObjects()[0] === headText);
      }
    }, [text])
    

    The solution is that you need to give headText its own state so that any subsequence calls to App() the object reference is preserved.

    const [headText, setTextObject] = useState(
    new fabric.Text('test', {
       fill: 'black',
       top: 50,
       left: 50,
     }));