-
-
Save erikras/5ca8dc618bae5dbc8faf7d2324585d01 to your computer and use it in GitHub Desktop.
import React, { Component, PropTypes } from 'react' | |
class RichTextMarkdown extends Component { | |
static propTypes = { | |
input: PropTypes.shape({ | |
onChange: PropTypes.func.isRequired, | |
value: PropTypes.string | |
}).isRequired | |
} | |
constructor(props) { | |
super(props) | |
this.state = { value: undefined } | |
} | |
componentDidMount() { | |
this.RichTextEditor = window.RichTextEditor | |
this.setState({ | |
value: this.props.input.value ? | |
this.RichTextEditor.createValueFromString(this.props.input.value, 'markdown') : | |
this.RichTextEditor.createEmptyValue() | |
}) | |
} | |
handleChange = value => { | |
this.setState({ value }) | |
let markdown = value.toString('markdown') | |
if(markdown.length === 2 && markdown.charCodeAt(0) === 8203 && markdown.charCodeAt(1) === 10) { | |
markdown = '' | |
} | |
this.props.input.onChange(markdown) | |
} | |
render() { | |
const { RichTextEditor, state: { value }, handleChange } = this | |
return RichTextEditor ? <RichTextEditor value={value} onChange={handleChange}/> : <div/> | |
} | |
} | |
export default RichTextMarkdown |
Here's an example of instantiating the RTE component outside the render function.
class RichTextFieldArrayComponent extends React.Component {
constructor(props) {
super(props);
}
// The renderField const should go here
render() {
// The renderField const should be instantiated OUTSIDE the render function for performance reasons.
// However, it needs to be reloaded upon any change to the DOM, so here it is.
//
const renderField = ({ input, meta: { touched, error } }) => (
<div>
<RichTextMarkdown {...input} />
{touched && (error && <div className="formValidationErrorText">{error}</div>)}
</div>
);
return (
{fields.map((component, index) =>
<div key={index}>
<Field
name={`${component}.text`}
component={renderField}
/>
</div>
)}
);
}
}
I had some trouble with reset logic myself; and decided to use the componentWillReceiveProps
hook as workaround.
componentWillReceiveProps(nextProps) {
const wasSubmitting = this.props.meta.submitting;
const becomePristine = nextProps.meta.pristine;
if (wasSubmitting && becomePristine) {
const editorState = createEditorStateWithText(nextProps.input.value || '');
this.setState({
editorState,
});
}
}
Thanks @smeijer I also used the componentWillReceiveProps but changed for my situation
componentWillReceiveProps(nextProps) {
if (nextProps.submitSucceeded) {
this.setState({ textEditor: RichTextEditor.createEmptyValue() });
}
}
Very good @davidsonsns
Thanks @smeijer, @davidsonsns.
Confirming componentWillReceiveProps
is the answer!
I took a bit of a different approach that should be much better performance wise, as well as integrate completely with Redux Form.
import React, { Component, PropTypes } from 'react';
import RichTextEditor from 'react-rte';
class Wysiwyg extends Component {
static propTypes = {
i18n: PropTypes.object.isRequired,
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string
}
constructor (props){
super(props);
this.state = {
value: this.props.value === '' ? RichTextEditor.createEmptyValue() : RichTextEditor.createValueFromString(this.props.value, 'html'),
typing: false,
typingTimeOut: 0
};
this.onChange = this.onChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if(nextProps.value === '') {
// eslint-disable-next-line react/no-set-state
this.setState({
...this.state,
value: RichTextEditor.createEmptyValue()
});
}
}
onChange(value) {
if (this.state.typingTimeout) {
clearTimeout(this.state.typingTimeout);
}
// 0.25sec timeout for sending text value to redux state for performance
// eslint-disable-next-line react/no-set-state
this.setState({
value: value,
typing: false,
typingTimeout: setTimeout(() => {
let isEmpty = !value.getEditorState().getCurrentContent().hasText();
let val = isEmpty ? '' : value.toString('html');
this.props.onChange(val);
}, 250)
});
}
render() {
const { value } = this.state;
//Pass down i18n object to localise labels
const { i18n, onBlur } = this.props;
const toolbarConfig = {
// Optionally specify the groups to display (displayed in the order listed).
display: ['INLINE_STYLE_BUTTONS', 'BLOCK_TYPE_BUTTONS', 'LINK_BUTTONS', 'BLOCK_TYPE_DROPDOWN', 'HISTORY_BUTTONS'],
INLINE_STYLE_BUTTONS: [
{label: 'Bold', style: 'BOLD'},
{label: 'Italic', style: 'ITALIC'},
{label: 'Underline', style: 'UNDERLINE'}
],
BLOCK_TYPE_DROPDOWN: [
{label: 'Normal', style: 'unstyled'},
{label: 'Heading Large', style: 'header-one'},
{label: 'Heading Medium', style: 'header-two'},
{label: 'Heading Small', style: 'header-three'}
],
BLOCK_TYPE_BUTTONS: [
{label: 'UL', style: 'unordered-list-item'},
{label: 'OL', style: 'ordered-list-item'}
]
};
//editorStyle={{background: 'black', height: '300px'}} in next version to allow resizing on the fly
return (<RichTextEditor value={value} onChange={this.onChange} onBlur={onBlur} toolbarConfig={toolbarConfig} />);
}
}
export default Wysiwyg;
None of the above worked for me when I wanted to dynamically change the RichTextMarkdown after the form was initialized. The reason was onChange
changes the state and also calls this.props.onChange
which in turn triggers a props change and calls componentWillReceiveProps
which would cause the cursor to jump. I ended up comparing nextProps value with the state value in componentWillReceiveProps
and that works just fine. Here's the complete code:
import React, { Component, PropTypes } from 'react';
import RichTextEditor from 'react-rte';
class RichTextMarkdown extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
value: PropTypes.string
}
constructor(props) {
super(props);
this.state = {
value: this.props.value === '' ? RichTextEditor.createEmptyValue() : RichTextEditor.createValueFromString(this.props.value, 'html'),
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.value.toString('html')) {
this.setState({
value: nextProps.value ?
RichTextEditor.createValueFromString(nextProps.value, 'html') :
RichTextEditor.createEmptyValue()
})
}
}
onChange = (value) => {
this.setState({ value })
this.props.onChange(value.toString('html'))
}
render() {
return (
<RichTextEditor value={this.state.value} onChange={this.onChange} />
);
}
}
export default RichTextMarkdown;
@gsdean I am facing the same issue, if I use componentWillReceiveProps then cursor bounces around in the editor box. and serverside value is accessible only in nextProps.input.value and not in this.props.input.value. As componentWillReceiveProps always gets executed when value changes and set the state again and cursor bounce around.
did you had any solution for serverside data rendering with rte and redux-form?
@pbibalan your solution worked to compare the current state and next props. thank you!!
@pbibalan or anyone else, would you be able to provide a complete example of how you implemented and actually render the component in the form. I've literally spent 5 hrs on this now and am about to loose my mind.
Below attempt isn't recognized by redux-form. Initial value isn't available and value on submit...
<RichTextField name="overview" onChange={(value)=> value.toString('html')} />
Below attempt produces "Warning: Failed prop type: The prop onChange
is marked as required in RichTextMarkdown
, but its value is undefined
"....
<Field
component={RichTextField}
type="text"
name="overview"
onChange={(value)=> value.toString('html')}
/>
I would really appreciate some help here.
Thanks!
@cantuket Hey,
recently got solved almost the same issue
this solution probably solve your's:
<Field
component={RichTextField}
type="text"
name="overview"
input={{ value: whateverState || yourValue, onChange:(value)=> value.toString('html') }}
/>
there are the Shape proptype in subj gist:
static propTypes = {
input: PropTypes.shape({
onChange: PropTypes.func.isRequired,
value: PropTypes.string
}).isRequired
you should pass input object to Field component with value
and onChange
onChange can be your custom function:
i.e. myFunc =(val)=>{ doWhatEverWith(val)}
input={{ value: whateverState || yourValue, onChange:myFunc* }}
*or this.myFunc if you defined it inside a class
None of the above work for me. The editor gets rendered but it is not being picked up in redux form.
I can't believe there is not a single example of the actual usage of this component.
Here is what I have:
RichTextEditor component:
import React, { Component, PropTypes } from 'react';
import RichTextEditor from 'react-rte';
class RichTextMarkdown extends Component {
static propTypes = {
input: PropTypes.shape({
onChange: PropTypes.func.isRequired,
value: PropTypes.string
}).isRequired
};
constructor(props) {
super(props);
this.state = {
value: this.props.input.value === '' ? RichTextEditor.createEmptyValue() : RichTextEditor.createValueFromString(this.props.input.value, 'html'),
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.input.value !== this.state.value.toString('html')) {
this.setState({
value: nextProps.input.value ?
RichTextEditor.createValueFromString(nextProps.input.value, 'html') :
RichTextEditor.createEmptyValue()
});
}
}
onChange = (value) => {
this.setState({ value });
console.log(value.toString('html'));
this.props.input.onChange(value);
};
render() {
return (
<RichTextEditor value={this.state.value} onChange={this.onChange} />
);
}
}
export default RichTextMarkdown;
Actual usage of the component:
import RTE from '../../../elements/RichTextMarkdown ';
// ... form omitted
<Field
component={RTE}
type="text"
name="comment_closing_page"
value="test"
input={{ value: 'TEST', onChange: value => value.toString('html') }} />
When I submit the form I get all of my other fields but I can't get hold of the values for the text editor. Any one know why this is?
I can't believe there is not a single example of the actual usage of this component.
Yep, I'm too.
So, I fixed it.
import React, {Component} from 'react';
import RichTextEditor from 'react-rte';
class RichTextMarkdown extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.input.value === '' ?
RichTextEditor.createEmptyValue() :
RichTextEditor.createValueFromString(this.props.input.value, 'html')
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.input.value !== this.state.value.toString('html')) {
this.setState({
value: nextProps.input.value ?
RichTextEditor.createValueFromString(nextProps.input.value, 'html') :
RichTextEditor.createEmptyValue()
});
}
}
onChange(value) {
const isTextChanged = this.state.value.toString('html') != value.toString('html');
this.setState({value}, e => isTextChanged && this.props.input.onChange(value.toString('html')));
};
render() {
return (
<RichTextEditor value={this.state.value} onChange={this.onChange.bind(this)} />
);
}
}
export default RichTextMarkdown;
The problem is concurrent updates of component internal state and redux state. So, we need to put onChange dispatch in setState callback.
And you shouldn't specify input prop of Field, redux does it by itself. Use it like a simple input:
<Field name="description" component={RichTextMarkdown} />
@smeijer
A bit old posting, but could you explain why you put const wasSubmitting
?
In my case, putting the following part into OP's code solved the initialization issue with asynchronous API calls.
componentWillReceiveProps(nextProps) {
const isPristine = nextProps.meta.pristine;
if (nextProps.input.value && isPristine) {
this.setState({
value: this.RichTextEditor.createValueFromString(nextProps.input.value, 'markdown')
});
}
}
@Dem0n3D
Thanks!
based on this thread, I changed the componentWillRecieveProps
in order to support React 16
componentWillReceiveProps(nextProps) {
this.state.value.setContentFromString(nextProps.value, "html");
}
@Dem0n3D
Thank you so much!
@Dem0n3D
Thank you so much!
OK! I believe the answer is this.
In the doco for Field, using a stateless component, it says:
Well, we need the Field to rerender upon any changes, so this is actually desirable. I therefore defined the stateless function inside of the
render()
method, and now thereact-rte
field responds to events like formreset
.Understandably, performance isn't great with this workaround 😭