Search code examples
reactjschart.jsreact-chartjs

Why function is not updating with `useCallback` in React and Chart.js


Background: I'm using React and chart.js to display a bar chart. I'm using also the chartjs-plugin-dragdata and chartjs-plugin-zoom to drag and zoom my chart within certain limits.

What I'm trying to do: in the dragdata plugin there is the option to modify the onDragStart and onDragEnd functions with custom ones (doc: https://github.com/chrispahm/chartjs-plugin-dragdata) within the options object. I want to change this function whenever a certain state is updated. The state is updated in another component and I can listen to its change via the useEffect hook.

// works when setActiveState is called
useEffect(()=> {
    console.log(activeState) // changes from state_1 to state_2
}, [activeState]);

And in the actual code:

export const SomeComponent: FC = () => {
    const { activeState } = useContext(CertainContext)
    const data = //.. some data

    const onDragStart = (event, elIndex, chartIndex)=> {
        console.log(activeState) // <-- here I have the issue, always state_1
    }
    const options = {
        // other options
        plugins: {
            dragData: {
                onDragStart: onDragStart
            }
        }
    }
    return (
      <Chart
        type="bar"
        options={options}
        data={data}
      />
    )
}

The issue is that no matter what I try, the onDragStart function seems to "save" the first activeState value and never modifies or updates it.

What I tried: From my understanding it has to do with how react closes around values in functions, therefore I tried also to use the useCallback hook to recompute the function whenever the activeState changes, without success.

export const SomeComponent: FC = () => {
    const { activeState } = useContext(CertainContext)
    const data = //.. some data

    const onDragStart = useCallback((event, elIndex, chartIndex)=> {
        console.log(activeState) // <-- here I have the issue, always state_1
    }, [activeState])
    const options = {
        // other options
        plugins: {
            dragData: {
                onDragStart: onDragStart
            }
        }
    }
    return (
      <Chart
        type="bar"
        options={options}
        data={data}
      />
    )
}

I'm sure the component is re-rendering, but still that function in particular is not. Other values in the options are being updated. For example:

    const options = {
        // other options
        plugins: {
            zoom: {
                wheel: {
                    enabled: activeState === "state_1" // This actually is being updated and works
                }
            }
        }
    }

I also tried with useMemo for the options object itself and more combinations as well, but as you may have figured I'm not yet very used to how hooks are working. If necessary I can also provide more information


Solution

  • It looks like chartjs-plugin-dragdata registers events on inialization and doesn't update them later. You'd need to use useRef to get the current value.

    const { activeState } = useContext(CertainContext);
    
    const ref = useRef('');
    ref.current = activeState;
    
    ...
    
    const options = {
      // other options
      plugins: {
          dragData: {
              onDragStart: () => {
                console.log(ref.current);
              },
          }
      }
    }