Wrapping an external library as a controlled component with react hooks: problem with useEffect dependencies

760 views Asked by At

I'm trying to make a thin wrapper to the "jsoneditor" library using a functionnal component. I'm rather new to React and worked so far mainly with hooks. So I tried to adapt the example given by the author of the library to use hooks: https://github.com/josdejong/jsoneditor/tree/master/examples/react_demo

This is what I came up with so far:

import React, {useRef, useState, useEffect, useCallback} from 'react'
import JSONEditor from 'jsoneditor'
import styles from './JSONEditorReact.module.css'
import 'jsoneditor/dist/jsoneditor.css';


function App(){
  const [json, setJson] = useState({some_key:"some_value"})

  function onChangeJson(json){
    setJson(json)
  }

  return <JSONEditorReact onChangeJson={onChangeJson} json={json}/>
}



function JSONEditorReact({onChangeJson, json}){
  const containerRef = useRef()
  const editorRef = useRef() // used as a  namespace to have a reference to the jsoneditor object


  useEffect(
    () => {
      console.log("mounting")
      const options = {
    modes: ['tree','form','view','text'],
    onChangeJSON: onChangeJson
      }
      editorRef.current = new JSONEditor(containerRef.current, options)
      return () => editorRef.current.destroy()
    },
    [] //eslint complains about the missing dependency "onChangeJson" here
  )

  useEffect(
    () => {
      console.log("updating")
      editorRef.current.update(json)
    },
    [json]
  )

  return (
    <div className={styles.container} ref={containerRef} />
  )

}

export default App;

It works - but eslint complains about onChangeJson being a missing dependency in useEffect. If I add it as a dependency, useEffect runs each time the user inputs something into the json editor. This implies that the user looses focus on the editor each time he enters a character. My understanding is that when it occurs, setJson function of App is called, so App component is refreshed, causing the onChangeJson function to be-reinstanciated, so the first useEffect is rerun, and a new JSONEditor is instanciated.

I had some ideas but they don't seem satisfying:

  • define onChangeJson with useCallback - issue : I find it daunting to call useCallback each time I want to use my component
  • pass the setter function setJson of App as the "onChangeJson" property of JSONEditorReact - issue: what if I want to perform more actions than just setting the state in my callback?

Any more relevant ideas to solve the missing dependency issue without running the first useEffect on each input?

Is this a kind of use case where class components are more relevant than functional components using hooks? (the wrapper using class components looks more straightforward than mine, where I had to use a ref to create a namespace to hold my JSONEditor instance)

0

There are 0 answers