Search code examples
javascriptmongodbreactjsmeteordraftjs

How to display the saved Draft-JS data in the Editor? (React + Meteor)


I am trying to keep the saved content in the Editor (after refresh for instance).

  • Currently, the content comes from the DB to the console.log and displays as expected.
  • But I can't keep it in the Editor.

[Edit] Removed the draft project link, see solution below.

I'm using:
Draft-JS
react-draft-wysiwyg

Here's the ~/imports/collections/bins.js

    import { Mongo } from 'meteor/mongo';

    Meteor.methods({
      'bins.insert': function() {
        return Bins.insert({
          createdAt: new Date(),
          content: '',
          sharedWith: [],
          ownerId: this.userId
        });
      },

      'bins.remove': function(bin) {
        return Bins.remove(bin);
      },

      'bins.update': function(bin, content) {
        return Bins.update(bin._id, { $set: { content } });
      }
    });

    export const Bins = new Mongo.Collection('bins');

~/client/components/bins/bins_main.js

    import React, { Component } from 'react';
    import { createContainer } from 'meteor/react-meteor-data';
    import { Bins } from '../../../imports/collections/bins';
    import BinsEditor from './bins_editor';

    class BinsMain extends Component {
      render() {
        if (!this.props.bin) { return <div>Loading...</div>; }

        return (
          <div>
            <BinsEditor bin={this.props.bin} />
          </div>
        );
      }
    }

    export default createContainer((props) => {
      const { binId } = props.params;
      Meteor.subscribe('bins');

      return { bin: Bins.findOne(binId) };
    }, BinsMain);

Solution

  • Here is the example app - a complete example using Draft-JS + React + Meteor + Material-UI + Accounts-UI + Material-UI-Accounts.

    You can

    • Create a bin linked to your user account (if logged in)
    • Edit a bin
    • Autosave bin while editing

    Here is the bins_editor.js

        import React from "react";
        import {Editor, EditorState, ContentState, RichUtils, convertToRaw, convertFromRaw} from "draft-js";
    
        export default class BinsEditor extends React.Component {
    
          constructor(props) {
            super(props);
    
            let editorState;
    
    
            if (!this.props.bin.content) {
                this.state = {editorState: EditorState.createEmpty()};
                console.log('empty');
              } else {
                const rawContent = this.props.bin.content;
                const parsedContent = convertFromRaw(JSON.parse(rawContent));
    
                this.state = {editorState: EditorState.createWithContent(parsedContent)};
                console.log('not empty');
              }
                this.focus = () => this.refs.editor.focus();
                this.onChange = (editorState) => {
                  this.setState({editorState});
                  const contentState = editorState.getCurrentContent();
                  const rawContent = JSON.stringify(convertToRaw(contentState));
    
                  Meteor.call('bins.update', this.props.bin, rawContent);
                  console.log(rawContent);
                }
    
                this.handleKeyCommand = (command) => this._handleKeyCommand(command);
                this.toggleBlockType = (type) => this._toggleBlockType(type);
                this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
          }
    
          _handleKeyCommand(command) {
            const {editorState} = this.state;
            const newState = RichUtils.handleKeyCommand(editorState, command);
            if (newState) {
              this.onChange(newState);
              return true;
            }
            return false;
          }
    
          _toggleBlockType(blockType) {
            this.onChange(
              RichUtils.toggleBlockType(
                this.state.editorState,
                blockType
              )
            );
          }
    
          _toggleInlineStyle(inlineStyle) {
            this.onChange(
              RichUtils.toggleInlineStyle(
                this.state.editorState,
                inlineStyle
              )
            );
          }
    
          render() {
            const {editorState} = this.state;
    
            // If the user changes block type before entering any text, we can
            // either style the placeholder or hide it. Let's just hide it now.
            let className = 'RichEditor-editor';
            var contentState = editorState.getCurrentContent();
            if (!contentState.hasText()) {
              if (contentState.getBlockMap().first().getType() !== 'unstyled') {
                className += ' RichEditor-hidePlaceholder';
              }
            }
    
            return (
              <div className="RichEditor-root">
                <BlockStyleControls
                  editorState={editorState}
                  onToggle={this.toggleBlockType}
                />
                <InlineStyleControls
                  editorState={editorState}
                  onToggle={this.toggleInlineStyle}
                />
                <div className={className} onClick={this.focus}>
                  <Editor
                    blockStyleFn={getBlockStyle}
                    customStyleMap={styleMap}
                    editorState={editorState}
                    handleKeyCommand={this.handleKeyCommand}
                    onChange={this.onChange}
                    placeholder="Tell a story..."
                    ref="editor"
                    spellCheck={true}
                  />
                </div>
                <div>{JSON.stringify(convertToRaw(editorState.getCurrentContent()))}</div>
              </div>
            );
          }
        }
    
        // Custom overrides for "code" style.
        const styleMap = {
          CODE: {
            backgroundColor: 'rgba(0, 0, 0, 0.05)',
            fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
            fontSize: 16,
            padding: 2,
          },
        };
    
        function getBlockStyle(block) {
          switch (block.getType()) {
            case 'blockquote': return 'RichEditor-blockquote';
            default: return null;
          }
        }
    
        class StyleButton extends React.Component {
          constructor() {
            super();
            this.onToggle = (e) => {
              e.preventDefault();
              this.props.onToggle(this.props.style);
            };
          }
    
          render() {
            let className = 'RichEditor-styleButton';
            if (this.props.active) {
              className += ' RichEditor-activeButton';
            }
    
            return (
              <span className={className} onMouseDown={this.onToggle}>
                {this.props.label}
              </span>
            );
          }
        }
    
        const BLOCK_TYPES = [
          {label: 'H1', style: 'header-one'},
          {label: 'H2', style: 'header-two'},
          {label: 'H3', style: 'header-three'},
          {label: 'H4', style: 'header-four'},
          {label: 'H5', style: 'header-five'},
          {label: 'H6', style: 'header-six'},
          {label: 'Blockquote', style: 'blockquote'},
          {label: 'UL', style: 'unordered-list-item'},
          {label: 'OL', style: 'ordered-list-item'},
          {label: 'Code Block', style: 'code-block'},
        ];
    
        const BlockStyleControls = (props) => {
          const {editorState} = props;
          const selection = editorState.getSelection();
          const blockType = editorState
            .getCurrentContent()
            .getBlockForKey(selection.getStartKey())
            .getType();
    
          return (
            <div className="RichEditor-controls">
              {BLOCK_TYPES.map((type) =>
                <StyleButton
                  key={type.label}
                  active={type.style === blockType}
                  label={type.label}
                  onToggle={props.onToggle}
                  style={type.style}
                />
              )}
            </div>
          );
        };
    
        var INLINE_STYLES = [
          {label: 'Bold', style: 'BOLD'},
          {label: 'Italic', style: 'ITALIC'},
          {label: 'Underline', style: 'UNDERLINE'},
          {label: 'Monospace', style: 'CODE'},
        ];
    
        const InlineStyleControls = (props) => {
          var currentStyle = props.editorState.getCurrentInlineStyle();
          return (
            <div className="RichEditor-controls">
              {INLINE_STYLES.map(type =>
                <StyleButton
                  key={type.label}
                  active={currentStyle.has(type.style)}
                  label={type.label}
                  onToggle={props.onToggle}
                  style={type.style}
                />
              )}
            </div>
          );
        };