Search code examples
javascriptreactjsdraftjs

react-rte/draft-js: blockRenderMap not affecting the actual editor state


I am using blockRenderMap to give the option of small text while editing. By default react-rte does not support it. This is the code to do that:

import React, {Component} from 'react';
import RichTextEditor from 'react-rte';
import Immutable from 'immutable'
import Draft from 'draft-js'

const blockRenderMap = Immutable.Map({
    'small': {
      element: 'small'
    }
  });

// Include 'paragraph' as a valid block and updated the unstyled element but
// keep support for other draft default block types
const extendedBlockRenderMap = Draft.DefaultDraftBlockRenderMap.merge(blockRenderMap);

class MyStatefulEditor extends Component {

  state = {
    value: RichTextEditor.createEmptyValue()
  }

  onChange = (value) => {
    console.log(value.toString('html'))
    this.setState({value});
    if (this.props.onChange) {
      // Send the changes up to the parent component as an HTML string.
      // This is here to demonstrate using `.toString()` but in a real app it
      // would be better to avoid generating a string on each change.
      this.props.onChange(
        value.toString('html')
      );
    }
  };

  render () 
  { 
    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', className: 'custom-css-class'},
          {label: 'Italic', style: 'ITALIC'},
          {label: 'Underline', style: 'UNDERLINE'}
        ],
        BLOCK_TYPE_DROPDOWN: [
          {label : 'Small', style: 'small'},
          {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'}
        ]
      };
    return (  
      <RichTextEditor
        value={this.state.value}
        onChange={this.onChange}
        toolbarConfig={toolbarConfig}
        blockRenderMap={extendedBlockRenderMap}
      />
    );
  }

}

export default MyStatefulEditor;

While using the editor, this works fine enter image description here

But inside the onChange function, if I print the "value" variable, which contains the editorValue, it shows like this:

<p>asdas</p>

Instead of:

<small>asdas</small>

At the same time, if I select one of the default supported ones, like Heading Large: enter image description here

And I print value inside onChange, I see this:

<h1>asdas</h1>

Why does my blockRenderMap not work, while the default ones work? How can I make the value variable contain the expected small tags instead of the p tags, for the case it applies?


Solution

  • There were many struggles here

    1. react-rte does not support small font, so I had to use blockRenderMap to specify a new mapping, as described in the Draft.js API (which react-rte allows to pass via props)
    2. The blockRenderMap isn't good enough though. It only works during editing mode, so that approach was 50% of the solution (or 0, I didn't know). In fact Draft.js (don't think it is react-rte that does this, but can't find the exact Draft code that does this either) converts by default what it does not have already mapped as <p>. I really expected that the extension of the blockRenderMap will solve everything, but nope.
    3. The default mapping defined in Draft.js, is limiting: https://draftjs.org/docs/advanced-topics-custom-block-render-map/. A hacky idea was to use <section> as the style and then try to apply a CSS class to it. But section is not available by default. Even if you specify it as a custom blockRenderMap, it converts it to <p> in the value variable. So I ended up modifying <h4>.

    Solution was

    During editing:

    1. Specify a custom block style: https://draftjs.org/docs/advanced-topics-block-styling/. In my case a CSS class for small font:
    .rte-small-font {
      font-size: smaller;
      font-weight:normal;
      display:block;
      margin:0;
      padding:0;
    }
    
    1. Decide which one of the supported DraftJS blocks I will use: <h4> (because we were not using it anyway). The CSS class is basically converting the <h4> to <small>
    2. Add this item in BLOCK_TYPE_DROPDOWN: {label : 'Small', style: 'header-four'},

    And then, when onChange() gets called (basically 'saving' the edited text):

    1. Create a new string inside onChange() by replacing <h4> with <small>: let newString = value.toString('html').replace("<h4>", "<small>").replace("</h4>", "</small>");
    2. Pass that string to another function that we have, which is basically populating the content of a 'webpart' with that string.