Search code examples
javascriptreactjsfabricjs

React hooks not returning updated state values inside callback function from library events (FabricJS)


I am not getting the updated state values inside FabricJS events like object:modified, mouse:up, etc... But I can able to set state value inside that callback functions.

When I tried to get the state value, it is returning the initial values and not the updated one.

Example:

const [count, setCount] = useState(0);

The initial value is 0. I update the value using setCount to 1. Whenever I tried to get the "count" inside the callback function, it is returning 0 or the initial value.

Edit:

What I am trying to achieve is that I am trying to save FabricJS object whenever object modified (For Undo, Redo operations). Whenever an object changes, FabricJS is triggering an event and calls a function in the component. But I don't get the state value.

Code:

function MyFunction()
{
   const [canvasObj, setCanvasObj] = useState({});
   const [state, setCanvasState] = useState('');
   const [undo, setUndo] = useState([]);

   useEffect(() => {

      if(Object.keys(canvasObj).length == 0) {
         var _canvas = new fabric.Canvas('canvas', {
            preserveObjectStacking: true,
         });

         setCanvasObj(_canvas);

         _canvas.on("object:modified", saveState);
      }
   });

   function saveState() {
       console.log(canvasObj); // Returns null even though object initialised
       if (state) { // state value returns null. Due to this, I can't get the existing data and push the current state
          let newState = undo;
          newState.push(state);
          setUndo(newState);
       }
       const tmpState = JSON.stringify(canvasObj);
       setCanvasState(tmpState);
   }
}

Solution

  • Here is the general example on how can you save canvas state inside react component. You need a separate variable to keep track of canvas history changes:

    import React, { useRef, useEffect, useCallback, useState } from "react";
    import { fabric } from "fabric";
    
    export default function App() {
      const canvasRef = useRef();
      const [canvas, setCanvas] = useState();
      // Array of objects, that represents canvas state history
      const [canvasHistory, setCanvasHistory] = useState([initialState]);
      const [canvasState, setCanvasState] = useState(initialState);
    
      const onObjectModified = useCallback(
        e => {
          const newCanvasState = e.target.canvas.toJSON();
          setCanvasState(newCanvasState);
          // Limit history depth
          setCanvasHistory(history => [...history, newCanvasState].slice(-4));
        },
        [setCanvasState, setCanvasHistory]
      );
    
      useEffect(() => {
        const canvas = new fabric.Canvas(canvasRef.current);
        canvas.loadFromJSON(initialState);
    
        canvas.on("object:modified", onObjectModified);
        setCanvas(canvas);
    
        // Don't forget to destroy canvas and remove event listeners on component unmount
        return () => canvas.dispose();
      }, [canvasRef, onObjectModified, setCanvas]);
    
      const moveHistory = useCallback(
        step => {
          const currentStateIndex = canvasHistory.indexOf(canvasState);
          const prevState = canvasHistory[currentStateIndex + step];
          canvas.loadFromJSON(prevState);
          setCanvasState(prevState);
        },
        [canvas, canvasState, canvasHistory, setCanvasState]
      );
    
      const onUndo = useCallback(() => moveHistory(-1), [moveHistory]);
    
      const onRedo = useCallback(() => moveHistory(1), [moveHistory]);
    
      return (
        <div>
          <button onClick={onUndo} disabled={canvasHistory[0] === canvasState}>
            Undo
          </button>
          <button
            onClick={onRedo}
            disabled={canvasHistory[canvasHistory.length - 1] === canvasState}
          >
            Redo
          </button>
          <canvas ref={canvasRef} width="300" height="300" />
        </div>
      );
    }
    

    Also, I made a minimal working example here