react-codemirror2
react-codemirror2 copied to clipboard
CodeMirror2 refresh
Hello,
I'm currently building app based on react-codemirror2 and reactstrap. I have editor component inside Collapse component. Unfortunately with this setup, editor component is always blank until I click on it.
Based on this issues: https://stackoverflow.com/questions/8349571/codemirror-editor-is-not-loading-content-until-clicked https://stackoverflow.com/questions/10575833/codemirror-has-content-but-wont-display-until-keypress https://stackoverflow.com/questions/5364909/javascript-codemirror-refresh-textarea/5377029#5377029
I need to call .refresh()
method on CodeMirror instance.
I've tried to set the instance with editorDidMount()
and then calling this.instance.refresh()
in componentDidMount()
function, but this not helped.
Code:
import React, { Component } from 'react';
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import {faTrashAlt} from "@fortawesome/fontawesome-free-solid/index";
import {Button, ButtonGroup, Card, CardBody, Col, Collapse, Container, FormGroup, Label, Row} from "reactstrap";
import {Controlled as CodeMirror} from 'react-codemirror2'
require('codemirror/lib/codemirror.css');
require('codemirror/mode/javascript/javascript.js');
require('codemirror/mode/htmlmixed/htmlmixed.js');
class CodeBlock extends Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
collapse: false,
value: props.initialValue,
mode: props.mode
};
this.instance = null;
}
toggle() {
this.setState({ collapse: !this.state.collapse });
}
componentDidMount() {
// setTimeout(() => {this.instance.refresh()}, 0); // Doesn't work
this.instance.refresh();
}
render() {
return (
<div className="mb-3">
<Button color="outline-secondary" onClick={this.toggle} className="w-100">Block {this.state.mode}</Button>
<Collapse isOpen={this.state.collapse}>
<Card>
<CardBody>
<Container>
<Row>
<Col xs="2">
<FormGroup>
<Label>Actions</Label>
<div>
<ButtonGroup>
<Button className="btn" color="danger"><FontAwesomeIcon icon={faTrashAlt}/></Button>
</ButtonGroup>
</div>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<CodeMirror
value={this.state.value}
options={{
mode: this.state.mode,
lineNumbers: true,
autoRefresh:true
}}
editorDidMount={editor => { this.instance = editor }}
onBeforeChange={(editor, data, value) => {
this.setState({value});
}}
onChange={(editor, data, value) => {
}}
/>
</Col>
</Row>
</Container>
</CardBody>
</Card>
</Collapse>
</div>
)
}
}
export default CodeBlock;
I guesst the component mounts before editorDidMount
is called.
Try to refresh as soon as you have the instance (ie in the editorDidMount
callback).
Nope. After some testing with console.log()
, editorDidMount
is first, second is componentDidMount
.
Refreshing editor inside editorDidMount
blocks editor, and I can't even type.
setTimeout(() => {this.instance.refresh()}, 3000);
doesn't work too.
@dyzajash have you seen the bit on [autorefresh]?(https://codemirror.net/doc/manual.html#addon_autorefresh)
This addon can be useful when initializing an editor in a hidden DOM node, in cases where it is difficult to call refresh when the editor becomes visible. It defines an option autoRefresh which you can set to true to ensure that, if the editor wasn't visible on initialization, it will be refreshed the first time it becomes visible. This is done by polling every 250 milliseconds (you can pass a value like {delay: 500} as the option value to configure this). Note that this addon will only refresh the editor once when it first becomes visible, and won't take care of further restyling and resizing.
I looks like the second most StackOverflow answer goes this route with success. Try and let me know?
I saw it, but anyone have idea how to use it with react & webpack?
Addon is not added to npm package, simply downloading & requiring from app folder is not working.
I think I need to edit plugin source or place it manually inside node_modules folder. I will update this comment, when I try these two options.
Hi, @dyzajash I have the same problem with you. I think there have two method to solve it. One, according to this link https://codemirror.net/doc/manual.html#addon_autorefresh, add autorefresh to the options, like autoRefresh{delay:500}. But,the source of the autorefresh code :
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
if (cm.state.autoRefresh) {
stopListening(cm, cm.state.autoRefresh)
cm.state.autoRefresh = null
}
if (val && cm.display.wrapper.offsetHeight == 0)
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
})
If offsetHeight is equal zero. It will go right. Otherwise...... Second the solve was like you said ,add refresh method to the source code of react-codemirror2
@dyzajash @divefox I understand the issue more clearly now, thanks for the latest explanation. I looked at that plugin and have no problem baking that into the source. It's certainly small enough to pull off without much maintenance moving forward. I'll get a new release out this weekend. Thanks for the collaboration on this!
@scniro Thanks !
@scniro I think you needn't update the source code.I know where to refresh the code.
@dyzajash You can use this code instead of old
editorDidMount={editor => { this.instance = editor; this.instance.refresh() }}
and remove componentDidMount().
In my code, I do this, It's OK. I don't know why in componentDidMount to refresh codemirror is bad
@divefox that's odd, didn't we essentially try to do that? Either way I didn't get the free time to get this in over the weekend anyways 😞 Are you suggesting this should work as-is? Would be stoked if @dyzajash could confirm
@scniro In that way, It is works for me, I can debug into codemirror refresh method. Needs @dyzajash to confirm. @scniro In the post, first get editor and then in componentDidMount method to refresh editor.It is not worked. But get editor and then refresh. It is works.
I'll test this today and update comment :)
@dyzajash great look forward to it just keep us posted 😺
Sorry for late update.
import React, { Component } from 'react';
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import {faTrashAlt} from "@fortawesome/fontawesome-free-solid/index";
import {Button, ButtonGroup, Card, CardBody, Col, Collapse, Container, FormGroup, Label, Row} from "reactstrap";
import {Controlled as CodeMirror} from 'react-codemirror2'
require('codemirror/lib/codemirror.css');
require('codemirror/mode/javascript/javascript.js');
require('codemirror/mode/htmlmixed/htmlmixed.js');
export default class CodeBlock extends Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
collapse: false,
editorValue: props.initialValue,
mode: props.mode
};
}
toggle() {
this.setState({ collapse: !this.state.collapse });
}
render() {
return (
<div className="mb-3">
<Button color="outline-secondary" onClick={this.toggle} className="w-100">Blok kodu {this.state.mode}</Button>
<Collapse isOpen={this.state.collapse}>
<Card>
<CardBody>
<Container>
<Row>
<Col xs="2">
<FormGroup>
<Label>Akcje</Label>
<div>
<ButtonGroup>
<Button className="btn" color="danger"><FontAwesomeIcon icon={faTrashAlt}/></Button>
</ButtonGroup>
</div>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<CodeMirror
value={this.state.editorValue}
options={{
mode: this.state.mode,
lineNumbers: true,
autoRefresh:true
}}
editorDidMount={editor => { this.instance = editor; this.instance.refresh() }}
onBeforeChange={(editor, data, value) => {
this.setState({value});
}}
/>
</Col>
</Row>
</Container>
</CardBody>
</Card>
</Collapse>
</div>
)
}
}
EditorDidMount method is not working for me.
Is this issue solved? I have same issue to use this component T_T
I just solve this problem with this trick.
<Col>
{
this.state.collapse && (<CodeMirror
value={this.state.editorValue}
options={{
mode: this.state.mode,
lineNumbers: true
}}
onBeforeChange={(editor, data, value) => {
this.setState({value});
}}
/>)
}
</Col>
but it has little bit slow rendering. waiting for graceful solution.
Problem
CodeMirror has autorefresh addon but the code below doesn't work.
import { UnControlled as CodeMirror } from 'react-codemirror2';
require('codemirror/addon/display/autorefresh');
(snip)
render() {
return <CodeMirror
options={{
autoRefresh: true
}}
/>
}
Cause
cm.display.wrapper.offsetHeight
of autorefresh.js#19 may NOT become zero depending on the timing of initialization. In those case startListening
will not be triggered.
Solution
- Create
autorefresh.ext.js
/** * extends codemirror/addon/display/autorefresh * * @author Yuki Takei <[email protected]> * @see https://codemirror.net/addon/display/autorefresh.js * @see https://github.com/scniro/react-codemirror2/issues/83#issuecomment-398825212 */ /* eslint-disable */ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: http://codemirror.net/LICENSE (function(mod) { mod(require("codemirror")); })(function(CodeMirror) { "use strict" CodeMirror.defineOption("autoRefresh", false, function(cm, val) { if (cm.state.autoRefresh) { stopListening(cm, cm.state.autoRefresh) cm.state.autoRefresh = null } if (val && (val.force || cm.display.wrapper.offsetHeight == 0)) startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250}) }) function startListening(cm, state) { function check() { if (cm.display.wrapper.offsetHeight) { stopListening(cm, state) if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight) cm.refresh() } else { state.timeout = setTimeout(check, state.delay) } } state.timeout = setTimeout(check, state.delay) state.hurry = function() { clearTimeout(state.timeout) state.timeout = setTimeout(check, 50) } CodeMirror.on(window, "mouseup", state.hurry) CodeMirror.on(window, "keyup", state.hurry) } function stopListening(_cm, state) { clearTimeout(state.timeout) CodeMirror.off(window, "mouseup", state.hurry) CodeMirror.off(window, "keyup", state.hurry) } });
- Add
force
optionimport { UnControlled as CodeMirror } from 'react-codemirror2'; require('path/to/autorefresh.ext'); (snip) render() { return <CodeMirror options={{ autoRefresh: {force: true} }} /> }
This is my code. It is worked.
import React from 'react';
import PropTypes from 'prop-types';
import { UnControlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/keymap/sublime';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/edit/closebrackets';
import 'codemirror/addon/comment/comment';
import 'codemirror/addon/search/match-highlighter';
import 'codemirror/mode/gfm/gfm';
import 'codemirror/mode/python/python';
import 'codemirror/addon/display/placeholder';
import 'codemirror/lib/codemirror.css';
import 'styles/mixins/codemirror.less';
export default class CM extends React.PureComponent {
render() {
return (
<CodeMirror
{...this.props}
editorDidMount={(editor) => {
editor.refresh();
}}
options={Object.assign(
{
mode: 'python',
lineNumbers: true,
highlightSelectionMatches: true,
indentUnit: 4,
tabSize: 4,
lineWrapping: true,
dragDrop: false,
keyMap: 'sublime',
matchBrackets: true,
autoCloseBrackets: true,
},
this.props.options || {}
)}
/>
);
}
}
CM.propTypes = {
options: PropTypes.object.isRequired,
};
has there been any progress on this? Looking over the issue I see lots of code without a clear solution. Does the answer lie within a change to the repo or is this handled such as the various examples throughout this thread? Maybe identifying the best approach here and documenting in the readme? PR's always welcome
(I am a new user of react-codemirror2 as of yesterday. Thanks for this great package!)
I just encountered this issue. CodeMirror's autorefresh addon did not work for me. @yuki-takei's autorefresh.ext did work for me. Perhaps the elegant solution is to request a fix to CodeMirror's autorefresh similar to autorefresh.ext so that it works in the case of a React component?
I have posted a PR. Let’s keep our fingers crossed :innocent:
I also encountered this problem. The direct cause of this problem can be referred to the following code.
import React, { Component } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/mode/python/python';
import 'codemirror/lib/codemirror.css';
class CmTest extends Component {
state = {
text : '#!/usr/bin/env python\n',
showCm: false,
showBlock: false,
};
changeCm = () => {
this.setState({ showCm: true})
};
changeBlock = () => {
this.setState({ showBlock: true})
};
render() {
const { text, showCm, showBlock } = this.state;
// solution 1:
// if(this.instance) {
// setTimeout(()=> this.instance.refresh(), 200);
// }
return (<div>
react-codemirror2 test
<div><button onClick={this.changeCm}>one</button></div>
<div><button onClick={this.changeBlock}>two</button></div>
<div style={ !showBlock ? {display: 'none'}: {}}>
this is block
{
// solution 2:
// showBlock && showCm && <CodeMirror
showCm && <CodeMirror
value={text}
options={{
mode: 'python',
lineNumbers: true,
autoRefresh:true
}}
editorDidMount={editor => {
this.instance = editor;
}}
onBeforeChange={(editor, data, value) => {
this.setState({ text: value });
}}
/>
}
</div>
</div>);
}
}
export default CmTest;
If you click button 1 and then click button 2, the bug is reproduced.
There are two solutions here. One solution can add the following code to the render function.
if(this.instance) {
setTimeout(()=> this.instance.refresh(), 200);
}
Another solution needs to control CodeMirror not to initialize too early.
This is my code. It is worked.
import React from 'react'; import PropTypes from 'prop-types'; import { UnControlled as CodeMirror } from 'react-codemirror2'; import 'codemirror/mode/markdown/markdown'; import 'codemirror/keymap/sublime'; import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/comment/comment'; import 'codemirror/addon/search/match-highlighter'; import 'codemirror/mode/gfm/gfm'; import 'codemirror/mode/python/python'; import 'codemirror/addon/display/placeholder'; import 'codemirror/lib/codemirror.css'; import 'styles/mixins/codemirror.less'; export default class CM extends React.PureComponent { render() { return ( <CodeMirror {...this.props} editorDidMount={(editor) => { editor.refresh(); }} options={Object.assign( { mode: 'python', lineNumbers: true, highlightSelectionMatches: true, indentUnit: 4, tabSize: 4, lineWrapping: true, dragDrop: false, keyMap: 'sublime', matchBrackets: true, autoCloseBrackets: true, }, this.props.options || {} )} /> ); } } CM.propTypes = { options: PropTypes.object.isRequired, };
this worked for me
I tried to do a very minimal example of how I'm working around this. The core of the matter is calling the refresh exactly once. This is done by abusing React state.
import React, { useState } from "react";
import { UnControlled as CodeEditor } from "react-codemirror2";
const WorkingExample = () => {
const [editor, setEditor] = useState();
if (editor) {
editor.refresh();
setEditor(undefined);
}
return (
<CodeEditor
editorDidMount={ed => {
setEditor(ed);
}}
/>
);
};
export default WorkingExample;
I removed the irrelevant props here. Here's the logic behind this solution:
- We define a hook for some state called
editor
, with a correspondingsetEditor
. It's initialized toundefined
- If
editor
is a truthy value, we calleditor.refresh
, in this caseeditor
is undefined, so we ignore this block. - Inside our
editorDidMount
we callsetEditor
with the value of the editor coming from that function, that will trigger a re-render, we go back to the top of our render function - We get to the
if (editor)
block again, this time around it does have a truthy value (An object), we calleditor.refresh()
inside here, and then callsetEditor
to set this toundefined
again, we don't want to calleditor.refresh()
every time we re-render. - Our editor was refreshed, and should display properly, since
editorDidMount
is only called once we don't re-seteditor
there, and life continues as usual.
Your console output, if you were try to log out the value of editor
should be something like:
undefined
CodeMirror {options: {…}, doc: Doc, display: Display, state: {…}, curOp: null, …}
undefined
...
A fuller example would be something like:
import React, { useState } from "react";
import { UnControlled as CodeEditor } from "react-codemirror2";
import "codemirror/mode/htmlmixed/htmlmixed";
const WorkingExample = ({ preview }) => {
const [editor, setEditor] = useState();
if (editor) {
editor.refresh();
setEditor(undefined);
}
return (
<CodeEditor
value={preview}
options={{
lineWrapping: true,
lineNumbers: true,
mode: "htmlmixed",
readOnly: true,
}}
editorDidMount={ed => {
setEditor(ed);
}}
/>
);
};
export default WorkingExample;
Cheers!
@dyzajash @Zyst @rmelian @yuki-takei @jo8937 @divefox @cristiano-belloni I am a lot shorter on time these days as when I started this project. Codemirror & React APIs are moving to quickly for me to keep atop of for the day-to-day. I am looking for a co-maintainer of this project. Please contact me directly if you are interested. Thank you for understanding.
I use react-codemirror2 and wanted to report an issue. After seeing lot of labels "help wanted" I backed off. Just like react-codemirror2 solves a problem in web development, I'm working on a tool to solve problems created by web frameworks
React APIs are moving to quickly for me to keep atop of for the day-to-day
@scniro @dyzajash @Zyst @rmelian @yuki-takei @jo8937 @divefox @cristiano-belloni I'm looking for feature requests for my project https://github.com/imvetri/ui-editor. It solves the problem I wanted to solve but I'm not sure how to re-design it so that developers can start using it.
Thanks for your time, -Vetrivel.
For me, the cause of this issue is that the CodeMirror component is mounted before the parent is added to the DOM, i.e. it is mounted on a detached DOM node. This is because I'm inside a React portal. Delaying a refresh by 100ms, for example, worked, but to have something more reliable I'm conditionally rendering CodeMirror to make sure it's mounted on an already attached DOM node. For example, in a Portal, you can use state as suggested in the official docs:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = { mounted: false };
this.portal = document.createElement('div');
}
componentDidMount() {
document.body.appendChild(this.portal);
this.setState({ mounted: true }); // Now it's safe to mount CodeMirror
}
componentWillUnmount() {
document.body.removeChild(this.portal);
}
render() {
return ReactDOM.createPortal((
<div>
{this.state.mounted &&
<CodeMirror />
}
</div>
), this.portal);
}
}
Below solution works for me
Steps:
- Create a new file called autorefresh.ext.js
- Call that file in Your component
- use autoRefresh: { force: true } in options of CodeMirror
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function (mod) {
mod(require('codemirror'));
}((CodeMirror) => {
CodeMirror.defineOption('autoRefresh', false, (cm, val) => {
if (cm.state.autoRefresh) {
stopListening(cm, cm.state.autoRefresh);
cm.state.autoRefresh = null;
}
if (val && (val.force || cm.display.wrapper.offsetHeight == 0)) { startListening(cm, cm.state.autoRefresh = { delay: val.delay || 250 }); }
});
function startListening(cm, state) {
function check() {
if (cm.display.wrapper.offsetHeight) {
stopListening(cm, state);
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight) { cm.refresh(); }
} else {
state.timeout = setTimeout(check, state.delay);
}
}
state.timeout = setTimeout(check, state.delay);
state.hurry = function () {
clearTimeout(state.timeout);
state.timeout = setTimeout(check, 50);
};
CodeMirror.on(window, 'mouseup', state.hurry);
CodeMirror.on(window, 'keyup', state.hurry);
}
function stopListening(_cm, state) {
clearTimeout(state.timeout);
CodeMirror.off(window, 'mouseup', state.hurry);
CodeMirror.off(window, 'keyup', state.hurry);
}
}));
Hi, I'm not sure if my issue with not refreshing of the text in CM is the same as yours, but I managed to solve it with simple shake-it-baby style re-render. Here's my code:
CodeMirrorEditor:
export default ({ value, onChange }) => {
if( !value ) return null // this line helps to re-render!!
const [ val, setVal ] = useState( value || '' )
useEffect( () => onChange( val ), [ val ] )
const setValue = ( _, __, v ) => setVal( v )
return <CodeMirror value={val} options={opts} onChange={setValue} onBeforeChange={setValue} />
}
The usage:
render() {
const { text } = this.state
return <div>
<button onClick={this.changeText( 'AAAAAAAAA' )}>click</button>
<CodeMirrorEditor value={text} onChange={console.info}/>
</div>
}
changeText = text => _ => this.setState( { text:null }, _ => this.setState( { text } ) )
so, I set the text in the state to null
1st, and then to the desired value.
Works like a charm!