4

I am trying to implement DraftJS within an existing functional component and I am unable to figure out how to do this. It appears that all of the documentation and user-submitted content refers to class components.

I try to set it up using the following:

import { Editor } from "react-draft-wysiwyg";
import { EditorState } from 'draft-js'

export default function myFunctionalComponent() {

    const [editorState, setEditorState] = useState(EditorState.createEmpty())

    return(
        <Editor 
            editorState={editorState}
            onChange={setEditorState}
        />
    )
}

However, unfortunately, I get this error in the console:

Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the r component.

Is there a way to make this work in a functional component?

Sam
  • 1,130
  • 12
  • 36
  • This could be an issue with `react-draft-wysiwyg` package. There is an open PR with the same issue in github: https://github.com/jpuri/react-draft-wysiwyg/pull/1243 – Bikram Karki Apr 03 '22 at 22:20

3 Answers3

3

As my very first answer in StackOverflow. :)

I took the example from https://github.com/facebook/draft-js/blob/main/examples/draft-0-10-0/rich/rich.html and converted it into a functional components 'RTEditor', .. .

Use the component with setContent as a prop. It takes the function to update parent elements state from useState


    const [content, setContent] = useState<any>({})
    ...
    <RTEditor setContent={setContent} />

RTEditor.tsx


    import React, { useState, useRef } from 'react'
    
    import {
      Editor,
      EditorState,
      RichUtils,
      getDefaultKeyBinding,
      ContentBlock,
      DraftHandleValue,
      convertFromHTML,
      convertFromRaw,
      convertToRaw,
      ContentState,
      RawDraftContentState,
    } from 'draft-js'
    import 'draft-js/dist/Draft.css'
    
    import BlockStyleControls from './BlockStyleControls'
    import InlineStyleControls from './InlineStyleControls'
    
    type Props = {
      setContent: (state: RawDraftContentState) => void
    }
    
    const RTEditor = ({ setContent }: Props) => {
      const editorRef = useRef(null)
      const [editorState, setEditorState] = useState(EditorState.createEmpty())
    
      const styleMap = {
        CODE: {
          backgroundColor: 'rgba(0, 0, 0, 0.05)',
          fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
          fontSize: 16,
          padding: 2,
        },
      }
    
      const getBlockStyle = (block: ContentBlock) => {
        switch (block.getType()) {
          case 'blockquote':
            return 'RichEditor-blockquote'
          default:
            return ''
        }
      }
    
      const onChange = (state: EditorState) => {
        setEditorState(state)
        setContent(convertToRaw(editorState.getCurrentContent()))
      }
    
      const mapKeyToEditorCommand = (e: any): string | null => {
        if (e.keyCode === 9 /* TAB */) {
          const newEditorState = RichUtils.onTab(e, editorState, 4 /* maxDepth */)
          if (newEditorState !== editorState) {
            onChange(newEditorState)
          }
          return null
        }
        return getDefaultKeyBinding(e)
      }
    
      const handleKeyCommand = (
        command: string,
        editorState: EditorState,
        eventTimeStamp: number
      ): DraftHandleValue => {
        const newState = RichUtils.handleKeyCommand(editorState, command)
        if (newState) {
          onChange(newState)
          return 'handled'
        }
        return 'not-handled'
      }
    
      const toggleBlockType = (blockType: string) => {
        onChange(RichUtils.toggleBlockType(editorState, blockType))
      }
    
      const toggleInlineStyle = (inlineStyle: string) => {
        onChange(RichUtils.toggleInlineStyle(editorState, inlineStyle))
      }
    
      return (
        <>
          <BlockStyleControls
            editorState={editorState}
            onToggle={toggleBlockType}
          />
          <InlineStyleControls
            editorState={editorState}
            onToggle={toggleInlineStyle}
          />
          <Editor
            ref={editorRef}
            editorState={editorState}
            placeholder='Tell a story...'
            customStyleMap={styleMap}
            blockStyleFn={(block: ContentBlock) => getBlockStyle(block)}
            keyBindingFn={(e) => mapKeyToEditorCommand(e)}
            onChange={onChange}
            spellCheck={true}
            handleKeyCommand={handleKeyCommand}
          />
        </>
      )
    }
    
    export default React.memo(RTEditor)

BlockStyleControls.tsx


    import React from 'react'
    import { EditorState } from 'draft-js'
    
    import StyleButton from './StyleButton'
    
    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' },
    ]
    
    type Props = {
      editorState: EditorState
      onToggle: (bockType: string) => void
    }
    
    const BlockStyleControls = ({ editorState, onToggle }: 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={onToggle}
              style={type.style}
            />
          ))}
        </div>
      )
    }
    
    export default React.memo(BlockStyleControls)

InlineStyleControls.tsx


    import React from 'react'
    import { EditorState } from 'draft-js'
    
    import StyleButton from './StyleButton'
    
    const INLINE_STYLES = [
      { label: 'Bold', style: 'BOLD' },
      { label: 'Italic', style: 'ITALIC' },
      { label: 'Underline', style: 'UNDERLINE' },
      { label: 'Monospace', style: 'CODE' },
    ]
    
    type Props = {
      editorState: EditorState
      onToggle: (bockType: string) => void
    }
    
    const InlineStyleControls = ({ editorState, onToggle }: Props) => {
      const currentStyle = editorState.getCurrentInlineStyle()
    
      return (
        <div className='RichEditor-controls'>
          {INLINE_STYLES.map((type) => (
            <StyleButton
              key={type.label}
              active={currentStyle.has(type.style)}
              label={type.label}
              onToggle={onToggle}
              style={type.style}
            />
          ))}
        </div>
      )
    }
    
    export default React.memo(InlineStyleControls)

StyleButton.tsx


    import React from 'react'
    
    type Props = {
      active: boolean
      style: string
      label: string
      onToggle: (bockType: string) => void
    }
    
    const StyleButton = ({ active, style, label, onToggle }: Props) => {
      const _onToggle = (e: any) => {
        e.preventDefault()
        onToggle(style)
      }
    
      const className = 'RichEditor-styleButton'
    
      return (
        <button
          className={className + `${active ? ' RichEditor-activeButton' : ''}`}
          onClick={_onToggle}
        >
          {label}
        </button>
      )
    }
    
    export default React.memo(StyleButton)

Sry for not covering all typings. Hope that helps.

Neil Girardi
  • 4,533
  • 1
  • 28
  • 45
2

I was able to solve this using React useCallback hook and it works for me.

import { EditorState } from 'draft-js'

export default function myFunctionalComponent() {

    const [editorState, setEditorState] = useState(EditorState.createEmpty())

     const onEditorStateChange = useCallback(
     (rawcontent) => {
       setEditorState(rawcontent.blocks[0].text);
     },
     [editorState]
   ); 
    return(
          <Editor
          placeholder="Tell a story..."
          onChange={onEditorStateChange}
        />
    )
}
Brisstone
  • 31
  • 1
  • 2
  • 6
-1

Build rich text editors in React using Draft.js and react-draft-wysiwyg by Jeremy Kithome. This guide to building rich text editors in React was last updated on 1 December 2022 by Shalitha Suranga to reflect updates to React. This update added sections about the benefits of react-draft-wysiwyg, more information on Draft.js and react-draft-wysiwyg, and a section on using the UseEffect Hook. To learn more about React Hooks check out our React Hooks reference guide and cheat sheet.