Search code examples
reactjsreact-hooksreactive-programmingcodemirror

React variable not reactive in external event


I'm a Vue developer that's trying to do a React App.

I'm trying to wrap CodeMirror in a React app and I'm running into some troubles. Not sure how to fix it.

I'm instantiating a CodeMirror editor and attaching some event handlers to it. Those handlers are like so: When you focus the editor, the isFocused variable becomes true. When you focus out of the editor, the isFocused variable becomes false. When you type in the editor you should do some action if the isFocused variable is true.

In the last handler, I need to access a reactive variable set with useState (isFocused). That variable is not reactive in the context of that function. It's read as the default value false.

The component looks something like this:

import React, {useState, useEffect, useRef} from 'react';
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/javascript/javascript'
import CodeMirror from 'codemirror'
import './App.css';

function App() {
  const textArea = useRef()
  const [isFocused, setIsFocused] = useState(false)
  const [data, setData] = useState('sample data')
  

  const handleChange = () => {
      console.log('isFocused->', isFocused);

    if (isFocused) {
     // do something here 
    }
  }

  const handleEditorFocus = () => {
    setIsFocused(true)
  }

  const handleEditorBlur = () => {
    setIsFocused(false)
  }

  useEffect(() => {
    const editor = CodeMirror.fromTextArea(textArea.current)
    editor.on('change', handleChange)
    editor.on('focus', handleEditorFocus)
    editor.on('blur', handleEditorBlur)

    return () => {
      editor.toTextArea()
    } 
  }, []);

  return (
    <div>
      {isFocused ? 'focused' : 'not focused'}
      <textarea
        ref={textArea}
        value={data}
        onChange={(e) => setData(e.target.value)}
      ></textarea>
    </div>
    )
};

export default App;

Please point me in the right direction with this issue.

Thank you!

UPDATE: I even made a repl.it here.

UPDATE 2 A better demo on stackblitz. I basically need to have access to the instance and change params in the handleChange function.


Solution

  • Add a useEffect function with a callback to handleChange and pass it isFocused as an argument.

    PS: Don't forget Boolean() to display the state of isFocused otherwise it returns an object.

    (See the demo link at the bottom of the page )

    import React, { useState, useEffect, useRef } from 'react';
    import 'codemirror/lib/codemirror.css';
    import 'codemirror/mode/javascript/javascript';
    import CodeMirror from 'codemirror';
    
    export default function App() {
      const textArea = useRef();
      const [isFocused, setIsFocused] = useState(false);
      const [data, setData] = useState('sample data');
    
      const handleChange = isF => { 
        console.log('isFocused->', Boolean(isF));           // here
    
        if (isFocused) {
          // ...
        }
      };
    
      const handleEditorFocus = () => {
        setIsFocused(true);
      };
    
      const handleEditorBlur = () => {
        setIsFocused(false);
      };
    
      useEffect(() => {                                     // here
        handleChange(isFocused);
      }, [isFocused]);
    
      useEffect(() => {
        const editor = CodeMirror.fromTextArea(textArea.current);
        editor.on('change', handleChange);
        editor.on('focus', handleEditorFocus);
        editor.on('blur', handleEditorBlur);
    
        return () => {
          editor.toTextArea();
        };
      }, []);
    
      return (
        <div>
          {isFocused ? 'focused' : 'not focused'}
          <textarea
            ref={textArea}
            value={data}
            onChange={e => setData(e.target.value)}
          />
        </div>
      );
    }
    

    Demo: Stackblitz