jsoneditor-react icon indicating copy to clipboard operation
jsoneditor-react copied to clipboard

Editor doesn't rerender when I change the 'value' props

Open dixyxavier opened this issue 7 years ago • 26 comments
trafficstars

I am using two editors at same time. An update in one editor should update the second one and viceversa. I have used same state variable in both editors and i have handled onChange in both editors by changing the state variable jsonData. Still when I change JSON data in one editor the other one doesn't rerender. It remains same. The following is my code:

`

<Editor value={this.state.jsonData} ajv={ajv} onChange={ (jsonData) => this.setState({ jsonData }) } search={false} navigationBar={false} /> <Editor value={this.state.jsonData} onChange={ (jsonData) => this.setState({ jsonData }) } mode="code" />
`

dixyxavier avatar Apr 23 '18 09:04 dixyxavier

<JsonEditor /> is uncontrolled component, so value property is only initial value of json editor. If your task is to use both values in 2 editors you need explicitly get jsoneditors.

For instance:

class YourComponent extends React.Component {
   set1EditorRef = instance => this.editor1 = instance;
   set2EditorRef = instance => this.editor2 = instance;

  1editorChangeHandler = jsonData => {
           this.setState({ jsonData });
          this.editor2.jsonEditor.set(jsonData);
   };

  2editorChangeHandler = jsonData => {
           this.setState({ jsonData });
          this.editor1.jsonEditor.set(jsonData);
   };

  render() {
     return [
           <Editor
                  ref={this.set1EditorRef}
                    value={this.state.jsonData}
                   ajv={ajv}
                   onChange={this.1editorChangeHandler}
                   search={false}
                   navigationBar={false}
             />
            <Editor
                     ref={this.set2EditorRef}
                     value={this.state.jsonData}
                     onChange={this.2editorChangeHandler}
                     mode="code"
             />
     ];
  }
}

vankop avatar Apr 23 '18 10:04 vankop

The main problem with controlled state is lossing focus in <JsonEditor />(it is a behaviour of josdejong/jsoneditor), thats why I did it as uncontrolled component.

vankop avatar Apr 23 '18 10:04 vankop

Thank you @vankop

dixyxavier avatar Apr 23 '18 12:04 dixyxavier

I have a similar problem, but a little different. I have one editor that I want to change it's contents based on a listbox selection. Currently, there doesn't seem to be a way to do that.

markdemich avatar Nov 29 '18 01:11 markdemich

@markdemich you need to get ref of <JsonEditor /> to get access to editor instance explicitly. For instance:

class YourComponent extends React.Component {
   setRef = instance => {
         if (instance) {
                const {jsonEditor} = instance;
               this.jsonEditor = jsonEditor;
         } else {
               this.jsonEditor = null;
         }
   };

  render() {
     return (
          <Editor
                     ref={this.setRef}
             />
    );
  }
}

then you can do what ever you want with jsonEditor using api

vankop avatar Nov 29 '18 18:11 vankop

I have a little different error. I have 2 tabs, and one contains default tree with json, the second has mode='code'. when I click on the second tab, nothing changed.

OlegRyzhkov avatar Jul 30 '19 16:07 OlegRyzhkov

@OlegRyzhkov do you using jsoneditor-react as uncontrolled component? If not follow the guide above https://github.com/vankop/jsoneditor-react/issues/3#issuecomment-383531421

vankop avatar Jul 30 '19 16:07 vankop

@OlegRyzhkov do you using jsoneditor-react as uncontrolled component? If not follow the guide above #3 (comment)

I exactly made it and it worked, but only if I render both at the same time. also, it rerender if I wrap one of the editor with div. looks like react can't know when to rerender element. how to you component as controlled?

OlegRyzhkov avatar Jul 31 '19 06:07 OlegRyzhkov

also, prop onChange doesn't work on json with error. how to know when user type something?

OlegRyzhkov avatar Jul 31 '19 12:07 OlegRyzhkov

better to control jsoneditor instance by yourself. Regarding to this example https://github.com/vankop/jsoneditor-react/issues/3#issuecomment-383531421.

componentDidMount() {
   if (!this.editor) return;

   const jsonEditor = this.editor.jsonEditor;

  // Use all jsoneditor API regarding to its version, see 
  // https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#methods
}

vankop avatar Jul 31 '19 12:07 vankop

@OlegRyzhkov do you using jsoneditor-react as uncontrolled component? If not follow the guide above #3 (comment)

I exactly made it and it worked, but only if I render both at the same time. also, it rerender if I wrap one of the editor with div. looks like react can't know when to rerender element. how to you component as controlled?

it could rerender if div above was destroyed. Small example:

function MyComponent({flag}) {
    return <div>{flag ? <div><JsonEdittor ... /></div> : null}</div>
}

vankop avatar Jul 31 '19 12:07 vankop

Editor wrapper updates only on https://github.com/vankop/jsoneditor-react/blob/master/src/Editor.jsx#L176

vankop avatar Jul 31 '19 12:07 vankop

This component should really be doing this https://github.com/vankop/jsoneditor-react/issues/3#issuecomment-383531421 itself. I don't see the point of leaving the user do all this boilerplat plumbing.

joan38 avatar Feb 07 '20 09:02 joan38

@joan38 It is possible, but in this case it will require a lot more maintenance =(

vankop avatar Feb 07 '20 14:02 vankop

I ended up rewriting this component myself using jsoneditor directly instead of depending on jsoneditor-react. It's actually very simple. I'd like to share this but I'm not using Javascript, I'm using Scala and it looks like this:

import "jsoneditor/dist/jsoneditor.min.css";

object JsonEditor {
  val component = FunctionalComponent[Props] { props =>
    val classes = useStyles()

    val (editor, updateEditor) = useState(null)
    val editorRef              = React.createRef[HTMLLabelElement]
    useEffect(
      () =>
        updateEditor(
          new JSONEditor(
            editorRef.current,
            {
              mode: props.mode,
              modes: props.modes.map(_.toJSArray),
              onChange: props.onChange,
              onChangeText: props.onChangeText
              // TODO Add all the options here
            },
            JSON.parse(props.value)
          )
        ),
      Seq.empty
    )
    useEffect(() => if (editor != null) editor.updateText(props.value), Seq(props.value))

    div(ref := editorRef, className := classes.editor)
  }
}

I also noticed you can avoid users of this lib to have to import "jsoneditor/dist/jsoneditor.min.css"; by adding the import in your component and configure your webpack as:

module.exports = {
  module: {
    rules: [
      { test: new RegExp("\\.css$"), loader: ["style-loader", "css-loader"] }
    ]
  }
}

joan38 avatar Feb 11 '20 21:02 joan38

@vankop thank you so much for your answer!

const JsonEditor = ({ data = {}, handleJSON}) => {
  const jsonEditorRef = useRef(null)

  useEffect(() => {
    if (jsonEditorRef.current !== null) {
      jsonEditorRef.current.set(data);
    }
  }, [data])

  const setRef = instance => {
    if (instance) {
      jsonEditorRef.current = instance.jsonEditor;
    } else {
      jsonEditorRef.current = null;
    }
  };

  return (
    <Editor
      ref={setRef}
      value={data}
      onChange={handleJSON}
      search={false}
      statusBar={true}
      mode='code'
      htmlElementProps={{
        style: {
          height: 500
        },
      }}
    />
  );
} 

uooopss avatar Jul 02 '20 15:07 uooopss

re- rendering single editor my code is constructor(props) { super(props); this.state = { deployements: [], }; this.editor1 = React.createRef(); } componentWillUpdate(nextProps, nextState) { console.log(nextState); this.editor1.current.set(nextState.deployements); console.log(this.editor1.current); console.log(this.editor1.current.props.value); } <Editor ref={this.editor1} value={this.state.deployements} onChange={this.editorChangeHandler} search={false} mode="code" navigationBar={false} />

z-war avatar Sep 01 '20 16:09 z-war

Thanks @uooopss Just a few minor tweaks to work as pretty much a dropin module.

File: JSONEditor.js

import React, { useRef, useEffect } from "react";
import { JsonEditor as Editor } from "jsoneditor-react";
import "jsoneditor-react/es/editor.min.css";

export default function JsonEditor({ value = {}, onChange }) {
  const jsonEditorRef = useRef(null);

  useEffect(() => {
    if (jsonEditorRef.current !== null) {
      jsonEditorRef.current.set(value);
    }
  }, [value]);

  const setRef = instance => {
    if (instance) {
      jsonEditorRef.current = instance.jsonEditor;
    } else {
      jsonEditorRef.current = null;
    }
  };

  return <Editor ref={setRef} value={value} onChange={onChange} />;
}

File: App.js

import Editor from "./JSONEditor";
import React, { useState } from "react";
export default function App() {
  const [data, setData] = useState({ test: "abc123" });
  return (
    <Editor onChange={data => setData(data)} value={data} />
    /* Add button or whatever to trigger an update/edit and just run setData(someJSON) */
  )
}

hutch120 avatar Nov 13 '20 00:11 hutch120

@hutch120 Thanks for sharing your solution. Maybe somebody knows how to implement that in mode="tree". For instance, if you try to edit something into an array, the tree will collapse this node after rerendering. Codesandbox example

PunKHS avatar Mar 05 '21 16:03 PunKHS

You can simply pass the key in the editor which makes it to re-render whenever the key values changes. I did like this:

<Editor key={props.value.type} {...props}

PS: Don't pass object for key since it will be converted as [object Object] and remains same even if values change. In my code props.value is an object and props.value.type is a string. so the editor re renders with new value for every value change, whereas type is a unique value.

sridharan21994 avatar Mar 17 '21 07:03 sridharan21994

@sridharan21994 can you elaborate on your fix here? I have the issue where as you edit the value, with each keystroke, the editor is re-rendered which makes it impossible to edit more than a single character at a time. I am trying to use the approach @hutch120 pasted above.

anthonywebb avatar Jun 24 '21 17:06 anthonywebb

Fix is found here https://github.com/vankop/jsoneditor-react/issues/4

anthonywebb avatar Jun 30 '21 14:06 anthonywebb

Thanks @uooopss Just a few minor tweaks to work as pretty much a dropin module.

File: JSONEditor.js

import React, { useRef, useEffect } from "react";
import { JsonEditor as Editor } from "jsoneditor-react";
import "jsoneditor-react/es/editor.min.css";

export default function JsonEditor({ value = {}, onChange }) {
  const jsonEditorRef = useRef(null);

  useEffect(() => {
    if (jsonEditorRef.current !== null) {
      jsonEditorRef.current.set(value);
    }
  }, [value]);

  const setRef = instance => {
    if (instance) {
      jsonEditorRef.current = instance.jsonEditor;
    } else {
      jsonEditorRef.current = null;
    }
  };

  return <Editor ref={setRef} value={value} onChange={onChange} />;
}

File: App.js

import Editor from "./JSONEditor";
import React, { useState } from "react";
export default function App() {
  const [data, setData] = useState({ test: "abc123" });
  return (
    <Editor onChange={data => setData(data)} value={data} />
    /* Add button or whatever to trigger an update/edit and just run setData(someJSON) */
  )
}

@hutch120, I really appreciate your solution above. I've tried replicating it in Typescript. But unfortunately, I'm stuck with this error Property 'set' does not exist on type 'never'. TS2339. I wasted much time in figuring out this error, I'm new to React and Typescript as well. Any help would be highly appreciated. Thanks! Here's my react component in JSONEditor.tsx :

const jsonEditor: React.FC<jsonProps> = function ({ value = {}, onChange }) {
	const jsonEditorRef = useRef(null);

	useEffect(() => {
		if (jsonEditorRef.current !== null) {
			jsonEditorRef.current.set(value);  // I'm getting error in this line
		}
	}, [value]);

	const setRef = (instance: any) => {
		if (instance) {
			jsonEditorRef.current = instance.jsonEditor;
		} else {
			jsonEditorRef.current = null;
		}
	};

hanoak avatar Dec 24 '21 14:12 hanoak

Thanks @uooopss Just a few minor tweaks to work as pretty much a dropin module. File: JSONEditor.js

import React, { useRef, useEffect } from "react";
import { JsonEditor as Editor } from "jsoneditor-react";
import "jsoneditor-react/es/editor.min.css";

export default function JsonEditor({ value = {}, onChange }) {
  const jsonEditorRef = useRef(null);

  useEffect(() => {
    if (jsonEditorRef.current !== null) {
      jsonEditorRef.current.set(value);
    }
  }, [value]);

  const setRef = instance => {
    if (instance) {
      jsonEditorRef.current = instance.jsonEditor;
    } else {
      jsonEditorRef.current = null;
    }
  };

  return <Editor ref={setRef} value={value} onChange={onChange} />;
}

File: App.js

import Editor from "./JSONEditor";
import React, { useState } from "react";
export default function App() {
  const [data, setData] = useState({ test: "abc123" });
  return (
    <Editor onChange={data => setData(data)} value={data} />
    /* Add button or whatever to trigger an update/edit and just run setData(someJSON) */
  )
}

@hutch120, I really appreciate your solution above. I've tried replicating it in Typescript. But unfortunately, I'm stuck with this error Property 'set' does not exist on type 'never'. TS2339. I wasted much time in figuring out this error, I'm new to React and Typescript as well. Any help would be highly appreciated. Thanks! Here's my react component in JSONEditor.tsx :

const jsonEditor: React.FC<jsonProps> = function ({ value = {}, onChange }) {
	const jsonEditorRef = useRef(null);

	useEffect(() => {
		if (jsonEditorRef.current !== null) {
			jsonEditorRef.current.set(value);  // I'm getting error in this line
		}
	}, [value]);

	const setRef = (instance: any) => {
		if (instance) {
			jsonEditorRef.current = instance.jsonEditor;
		} else {
			jsonEditorRef.current = null;
		}
	};

@hanoak You need to add a proper interface:

inteface JsonEditorRef{
   set: (val: any) => void;   // here we define `that set method`
}

const jsonEditorRef = useRef<JsonEditorRef>(null);
  
useEffect(() => {
      if(jsonEditorRef.current !== null){
           jsonEditorRef.current.set(value); // type error is gone.
      } 
}, [value]);
  

Hope helps!

xochilpili avatar Jan 09 '22 12:01 xochilpili

@xochilpili When I implement the above interface i get a new error Cannot assign to 'current' because it is a read-only property.ts(2540)

What should be the correct interface. Thanks in advance

loki2909 avatar Mar 25 '22 14:03 loki2909