Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
dixyxavier opened this issue Apr 23, 2018 · 26 comments
Open

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

dixyxavier opened this issue Apr 23, 2018 · 26 comments

Comments

@dixyxavier
Copy link

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"
/>

`
@vankop
Copy link
Owner

vankop commented Apr 23, 2018

<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
Copy link
Owner

vankop commented Apr 23, 2018

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.

@dixyxavier
Copy link
Author

Thank you @vankop

@markdemich
Copy link

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.

@vankop
Copy link
Owner

vankop commented Nov 29, 2018

@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

@OlegRyzhkov
Copy link

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.

@vankop
Copy link
Owner

vankop commented Jul 30, 2019

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

@OlegRyzhkov
Copy link

@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
Copy link

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

@vankop
Copy link
Owner

vankop commented Jul 31, 2019

better to control jsoneditor instance by yourself. Regarding to this example #3 (comment).

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
Copy link
Owner

vankop commented Jul 31, 2019

@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
Copy link
Owner

vankop commented Jul 31, 2019

@joan38
Copy link

joan38 commented Feb 7, 2020

This component should really be doing this #3 (comment) itself.
I don't see the point of leaving the user do all this boilerplat plumbing.

@vankop
Copy link
Owner

vankop commented Feb 7, 2020

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

@joan38
Copy link

joan38 commented Feb 11, 2020

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"] }
    ]
  }
}

@uooopss
Copy link

uooopss commented Jul 2, 2020

@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
        },
      }}
    />
  );
} 

@z-war
Copy link

z-war commented Sep 1, 2020

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);
}

@hutch120
Copy link

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) */
  )
}

@PunKHS
Copy link

PunKHS commented Mar 5, 2021

@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

@sridharan21994
Copy link

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.

@anthonywebb
Copy link

@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
Copy link

Fix is found here #4

@hanoak
Copy link

hanoak commented Dec 24, 2021

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;
		}
	};

@xochilpili
Copy link

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!

@loki2909
Copy link

@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

@edorivai
Copy link

Leaving this here with two purposes; (1) for future googlers, (2) to give people here the opportunity to tell my why this is a bad idea 😅.

Here we go:

<Editor value={myData} key={JSON.stringify(myData)} />
  • Simple, no ref juggling
  • Rerenders when myData changes

What am I missing? I should probably clarify that my use-case is 100% read-only. No actual editing needed. If you need editing and need to persist user input, you probably need to control whether or not the value prop should overwrite the editor contents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests