Build a rich text editor in React using Draft.js and react-draft-wysiwyg

Front-end developers often use plain text inputs or textareas for capturing elements of small size text segments, such as usernames, addresses, or cities. In some cases, they need to let users enter styled, formatted text with multimedia items in Web applications. However, plain text input cannot meet these requirements, so developers tend to use rich text editor concepts.

Rich text editors have become an integral part of how we interact with web applications, especially when it comes to content generation. Web-based rich text editors are typically WYSIWYG HTML editors that provide styles and real-time previews of formatted text segments. The Rich Text Editor enables you to control the appearance of text and gives content creators a powerful way to create and publish HTML anywhere.

In this article, we will use Draft.js and react-draft-wysiwyg to build a rich text editor and display the text we create using the editor.

What is Draft.js?

Draft.js is a rich text editor framework for React that provides developers with an API to build their own rich text editor implementation. It provides a pre-developed React component for generating and rendering rich text. Draft.js enables developers to build rich text editors for a variety of use cases by providing a declarative API that supports functionality ranging from simple text formatting to embedding media items such as hyperlinks, images, mentions, and more.

Draft.js does not provide pre-built toolbar elements. It provides atomic rich text editor building blocks as a framework, so if we need to create a rich text editor with a toolbar, we have to use a Draft.js based wrapper library or build one from scratch.

What is Reaction Draft WYSIWYG?

React -draft-wysiwyg library is a WYSIWYG (What You See Is What You Get) editor built using React and Draft.js libraries. It has many of the customizable built-in features you would expect from a modern HTML editor component, such as text style buttons, keyboard shortcut support, embedded media items, emoji support, and more.

Advantages of react-draft-wysiwyg

Fully featured software package

The React-draft-wysiwyg library provides a rich text editor component with all the regular WYSIWYG functionality and some advanced features, including mentions and hashtag support. You can install the pre-built editor into your application and have all the features you need!

Customizability and flexibility

The library does not limit the developer's scope of functionality by providing a strict feature set. It allows developers to customize toolbars and functionality through various component props. You can even add custom toolbar items easily!

Quick setup and available integrations

The React-draft-wysiwyg library also comes with a simple API, interesting notes - share valuable tutorials! Self-explanatory component properties and well-written documentation. This allows you to set up a rich text editor for your React application in record time. In addition, you can use the Draft.js core API and some popular Draft.js community libraries in the rich text editor component.

Get started with Draft.js and react-draft-wysiwyg

This tutorial assumes you have a working knowledge of React. Also, make sure Node.js, Yarn, or npm is installed on your computer. We will use Create React App to bootstrap our project.

Let's create our project in the directory of your choice. Any of the commands highlighted below can be typed into the command line to complete your project.

Enkesi: npx create-react-app draft-js-example

npm ( npm init <initializer> is available in npm v6+): npm init react-app draft-js-example

yarn (yarn create available in v0.25+): yarn create react-app draft-js-example

These will generate a new React project with the following directory structure:

draft-js-example-|
      |__node_mudles/
      |__public/
      |__src/
      |__.gitignore
      |__pacakge.json
      |__README.md
      |__yarn.lock

Navigate to the root of the project folder and run it:

cd draft-js-example
​
npm start
# --- or ---
yarn start

This will run the application in development mode and you can view it in your browser using http://localhost:3000/ :

Install required dependencies

The following commands install the Draft.js and react-draft-wysiwyg packages into your React project:

npm install install draft-js react-draft-wysiwyg
# --- or ---
yarn add draft-js react-draft-wysiwyg

Settings editor

To get started, we need the src/App.js file. We will import the editor component and styles from react-draft-wysiwyg and EditorState from draft-js.

This editor uses the default Draft.js editor without any styles. The Draft.js editor is built as a controlled ContentEditable component based on React's controlled input API. EditorState provides a snapshot EditorState. This includes undo/redo history, content, and cursor.

Let's add some code to display the rich text editor. Add the following code to your App.js file:

import React, { useState } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
​
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import './App.css';
​
function App() {
  const [editorState, setEditorState] = useState(
    () => EditorState.createEmpty(),
  );
​
  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
​
      <Editor
        editorState={editorState}
        onEditorStateChange={setEditorState}
      />
    </div>
  )
}
​
export default App;

We will start by using the createEmpty method of creating an empty state EditorState. This editor requires EditorState as a prop. You'll notice that after saving changes and showing updates, the view doesn't look that good:

Some changes need to be made to the App.css file. Replace its contents with the following:

.App-header {
  background-color: #282c34;
  min-height: 5vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
  margin-bottom: 5vh;
  text-align: center;
  padding: 12px;
 }

The editor is now positioned nicely in the browser view. Enter some text and type it by clicking the toolbar icon or pressing the keyboard shortcut (e.g. Control + B for bold text:

Editor style

If you look at the editor we rendered above, you'll notice that it's hard to tell where the text should be entered. Use style props to make different parts of the editor more visible. props can be classes that apply to specific parts, or objects containing styles:

  • wrapperClassName= "wrapper-class"

  • editorClassName= "editor-class"

  • toolbarClassName= "toolbar-class"

  • wrapperStyle= {<wrapperStyleObject>}

  • editorStyle= {<editorStyleObject>}

  • toolbarStyle= {<toolbarStyleObject>}

Add the className prop Editor component and related styles to App.css as follows to set the style of the editor:

App.js:

<Editor
  editorState={editorState}
  onEditorStateChange={setEditorState}
  wrapperClassName="wrapper-class"
  editorClassName="editor-class"
  toolbarClassName="toolbar-class"
/>

App.css:

.wrapper-class {
  padding: 1rem;
  border: 1px solid #ccc;
}
.editor-class {
  background-color:lightgray;
  padding: 1rem;
  border: 1px solid #ccc;
}
.toolbar-class {
  border: 1px solid #ccc;
}

The rich text editor should now look like this:

NOTE: You may notice a warning: "Can't call setState on a component" on development mode. React displays this warning message due to a problem in react-draft-wysiwyg. Hopefully the library maintainer will release a fix for this issue soon. Now you can disable React Strict mode as follows for this tutorial

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
​
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

See more details about this issue on GitHub.

with EditorState

Editors can be controlled or uncontrolled components. Controlled editors are implemented using EditorState, the editor's top-level state object.

While it is possible to create an uncontrolled editor using EditorState or RawDraftContentState, a stream type that represents the expected structure of the content's original format. If we wanted to create a controlled editor, we would pass it the following properties:

  • editorState: props for updating editor state in a controlled manner

  • onEditorStateChange: A function called when the editor state changes. This function takes an object parameter of type EditorState

After adding these to our editor component, it looks like this:

function App() {
  const [editorState, setEditorState] = useState(
    () => EditorState.createEmpty(),
  );

  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
      <Editor
        editorState={editorState}
        onEditorStateChange={setEditorState}
        wrapperClassName="wrapper-class"
        editorClassName="editor-class"
        toolbarClassName="toolbar-class"
      />
    </div>
  )
}

EditorState can also be used to create uncontrolled editors by passing defaultEditorState. This is an object of type EditorState that initializes the editor state after creating it:

function App() {
  const [editorState, setEditorState] = useState(
    () => EditorState.createEmpty(),
  );


  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
      <Editor
        defaultEditorState={editorState}
        onEditorStateChange={setEditorState}
        wrapperClassName="wrapper-class"
        editorClassName="editor-class"
        toolbarClassName="toolbar-class"
      />
    </div>
  )
}

Another way to implement an uncontrolled editor is to use RawDraftContentState. Uncontrolled editors adopt the following properties:

  • defaultContentState: An object of type RawDraftContentState is used to initialize the editor state after it is created.

  • onContentStateChange: A function called when the editor state changes, which takes an object parameter of type RawDraftContentState

An uncontrolled editor RawDraftContentState using the following implementation looks like this:

import React, { useState } from 'react';
import { ContentState, convertToRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import './App.css';

function App() {
  const _contentState = ContentState.createFromText('Sample content state');
  const raw = convertToRaw(_contentState);  // RawDraftContentState JSON
  const [contentState, setContentState] = useState(raw); // ContentState JSON


  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
      <Editor
        defaultContentState={contentState}
        onContentStateChange={setContentState}
        wrapperClassName="wrapper-class"
        editorClassName="editor-class"
        toolbarClassName="toolbar-class"
      />
    </div>
  )
}

export default App;

Once rendered, the rich text editor will be displayed as shown below, with preloaded text:

Use data conversion functions

As a user interacting with a rich text editor, you will undoubtedly need to save text or edit saved text. This means you should be able to convert ContentState to raw JavaScript and vice versa - or ContentState to HTML and vice versa. Draft.js provides three functions to help you do this:

  • ContentFromRaw: Convert raw state (RawDraftContentState) to ContentState. You can use this function to load the raw JSON structure saved in the database into a rich text editor instance

  • ContentToRaw: Convert ContentState to the original state. You can use this function to convert the current rich text editor state into a raw JSON structure for storage in the database

  • ContentFromHTML: Converts an HTML fragment into an object with two keys, one holding an array of ContentBlock objects and the other holding an entityMap. This object can then be used to construct content state from the HTML document

For our specific use case, we want to convert the editor state to HTML for storage and display. We can do this using libraries like Draftjs-to-html or Draft-convert. We will use Draft-convert since it has more features than Draftjs-to-html.

Run the following command in the terminal to install it:

npm install draft-convert
# --- or ---
yarn add draft-convert

Let's use the Draft-convert package to generate HTML from the editor state!

Create a preview EditorState for the converted content

Our rich text editor is ready to create some rich text, but not yet ready to save or preview what we've entered. You can use the editor and different toolbar options to learn how to create rich text. Here are examples of the different things I can do with the editor at this stage:

The next step is to convert the rich text to HTML. We will use the convertToHTML function Draft-convert to convert it, but first, we need to get the current content entered in the editor.

EditorState provides a method getCurrentContent to return the current ContentState of the editor, which we can then convert to HTML.

The way we handle changes to EditorState is going to have to change a bit. A handler will be called to update the EditorState, which stores the converted HTML in a JavaScript variable.

Use useEffect hook

Let's add a component that will take the editor content, convert it to HTML, and print the raw HTML string on the browser console:

import React, { useState, useEffect } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { convertToHTML } from 'draft-convert';

import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import './App.css';

function App() {
  const [editorState, setEditorState] = useState(
    () => EditorState.createEmpty(),
  );
  const [convertedContent, setConvertedContent] = useState(null);

  useEffect(() => {
    let html = convertToHTML(editorState.getCurrentContent());
    setConvertedContent(html);
  }, [editorState]);

  console.log(convertedContent);

  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
      <Editor
        editorState={editorState}
        onEditorStateChange={setEditorState}
        wrapperClassName="wrapper-class"
        editorClassName="editor-class"
        toolbarClassName="toolbar-class"
      />
    </div>
  )
}

export default App;

Please note that some editor formats may not support draft-convert and may cause errors.

The above code uses the useEffect hook and [editorState] to update the convertedContent state value of the dependent array. After running the above code and entering some styled text, you can see the raw HTML content of the current editor state on the browser console, like this:

Before structuring, the text entered in the editor has been transformed. Now, after rendering the JSX, we can display it. We will create a div which will display the entered text.

So to display text we will use dangerouslySetInnerHTML. You may ask yourself, why dangerously? By setting HTML from code, you expose yourself to cross-site scripting (XSS) attacks. The name is a subtle reminder of the dangers involved in doing so.

Clean up your HTML

You must ensure that the HTML is properly structured and sanitized before adding it to the page. An easy way is to use the dompurify library.

Install this package using the following command:

npm install dompurify
# --- or ---
yarn add dompurify

dangerouslySetInnerHTML receives an object with a __html key (two underscores). This key will save the sanitized HTML. We can now add methods to clean the HTML and the elements that hold it. Add import DOMPurify from 'dompurify';.

Next, replace the existing console.log statement with the following function implementation:

function createMarkup(html) {
  return {
    __html: DOMPurify.sanitize(html)
  }
}

Now, render the sanitized HTML in a div as follows:

<div className="App">
  <header className="App-header">
    Rich Text Editor Example
  </header>
  <Editor
    editorState={editorState}
    onEditorStateChange={setEditorState}
    wrapperClassName="wrapper-class"
    editorClassName="editor-class"
    toolbarClassName="toolbar-class"
  />
  <div
    className="preview"
    dangerouslySetInnerHTML={createMarkup(convertedContent)}>
  </div>
</div>

The createMarkup function receives an HTML string as a parameter and returns a sanitized HTML object. This method is called dangerouslySetInnerHTML with the content converted to HTML.

Add styles to HTML container

Finally, add some style HTML containers:

.preview {
  padding: 1rem;
  margin-top: 1rem;
}

Now we can enter text in the editor and see it appear below the rich text editor just as we formatted it:

That's it! We're fucked. We built a rich text editor that allows us to format text in various ways. The final code for the App.js file should be:

import React, { useState, useEffect } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { convertToHTML } from 'draft-convert';
import DOMPurify from 'dompurify';

import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import './App.css';

function App() {
  const [editorState, setEditorState] = useState(
    () => EditorState.createEmpty(),
  );
  const [convertedContent, setConvertedContent] = useState(null);

  useEffect(() => {
    let html = convertToHTML(editorState.getCurrentContent());
    setConvertedContent(html);
  }, [editorState]);

  function createMarkup(html) {
    return {
      __html: DOMPurify.sanitize(html)
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        Rich Text Editor Example
      </header>
      <Editor
        editorState={editorState}
        onEditorStateChange={setEditorState}
        wrapperClassName="wrapper-class"
        editorClassName="editor-class"
        toolbarClassName="toolbar-class"
      />
      <div
        className="preview"
        dangerouslySetInnerHTML={createMarkup(convertedContent)}>
      </div>
    </div>
  )
}

export default App;

Custom rich text editor component

The library defaults to rendering generic WYSIWYG functionality when you use an Editor without the need for custom components. For example, by default you get a toolbar with the features you'd expect in a rich text editor, such as text styling and adding embedded items. The React-draft-wysiwyg library is very flexible and full-featured, providing props for customizing toolbars, behaviors, and enabling additional functionality.

For example, you can create a minimal toolbar using the toolbar prop as follows:

<Editor
  editorState={editorState}
  onEditorStateChange={setEditorState}
  wrapperClassName="wrapper-class"
  editorClassName="editor-class"
  toolbarClassName="toolbar-class"
  toolbar={
  
  {
    options: ['inline', 'blockType']
  }}
/>

Watch the preview below:

Additionally, you can enable hashtags and mentions to support the following props:

hashtag={
  
  {
  separator: ' ',
  trigger: '#',
}}
mention={
  
  {
  separator: ' ',
  trigger: '@',
  suggestions: [
    { text: 'JavaScript', value: 'javascript', url: 'js' },
    { text: 'Golang', value: 'golang', url: 'go' },
  ],
}}

The above props can achieve the following functions:

You can view all supported functions in the official library documentation.

in conclusion

In this article, we look at creating a rich text editor that already has a toolbar with several options for entering and displaying text. We've only scratched the surface of the experiences you can create with react-draft-wysiwyg.

Find additional options and functionality in the react-draft-wysiwyg documentation, and explore how to build a more feature-rich editor. If you want to challenge yourself even more, you can add the ability to save text to a database, retrieve it, and update it. The code for this article can be found on GitHub. I can't wait to see what you build.

Guess you like

Origin blog.csdn.net/weixin_47967031/article/details/132884353