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.