In this tutorial, I'll cover how to build a wysiwyg text editor in React js. Follows YouTube tutorial(CodeAT21) and GitHub repository full source code Available.

React Quill powerful rich text editor

Setup

Create your react project. In your terminal/command line enter:

npx create-react-app text-editor
cd text-editor
npm install axios
npm install draft-js
npm install draftjs-to-html
npm install react-draft-wysiwyg
npm install react-router-dom
npm start 

Next, Project structure your folder as follows.

src
├── components
|   ├── Add.js
|   ├── Edit.js
|   ├── Editpost.js
|   └── Home.js
├── App.css
├── App.js
├── index.css
└── index.js 

Next, open up the App.js file located in the src folder. You should see this:

App.js
import React from 'react'; import {BrowserRouter as Router,Switch,Route} from "react-router-dom"; import './App.css'; import Home from './components/Home'; import Add from './components/Add'; import Edit from './components/Edit'; function App() { return ( <div> <Router basename="/"> <Switch> <Route exact path="/" component={Home}/> <Route path="/Add" component={Add}/> <Route path="/Edit/:postID" component={Edit}/> </Switch> </Router> </div> ); } export default App;
components/Home.js
import React,{useState,useEffect} from 'react'; import {Link } from "react-router-dom"; import axios from 'axios'; function Home() { useEffect(() => { viewPost(); }, []); const [ispost, setpost] = useState([]); const viewPost = async() =>{ try { await axios.get(`http://localhost:8080/allPost`,) .then(res => { if(res.data.success === true){ setpost(res.data.listall); } }) } catch (error) { throw error;} } return ( <div className="App"> <div className="container"> <div className="row"> <h1> React <span> wysiwyg </span> text editor </h1> <Link to="/Add" className="btn btn__theme btn__add"> Create New </Link> {ispost.map((item,index) => ( <div className="post__list" key={index}> <h2>{item.title}</h2> <div className="post__description" dangerouslySetInnerHTML={{ __html: item.description}} /> <Link to={`/Edit/${item.id}`} className="btn btn__theme"> Edit </Link> </div> ))} </div> </div> </div> ); } export default Home;
components/Add.js
import React,{useState} from 'react'; import { EditorState, convertToRaw} from 'draft-js'; import { Editor } from 'react-draft-wysiwyg'; import draftToHtml from 'draftjs-to-html'; import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; import { useHistory } from "react-router-dom"; import axios from 'axios'; function Add() { let history = useHistory(); const [userInfo, setuserInfo] = useState({ title: '', }); const onChangeValue = (e) => { setuserInfo({ ...userInfo, [e.target.name]:e.target.value }); } let editorState = EditorState.createEmpty(); const [description, setDescription] = useState(editorState); const onEditorStateChange = (editorState) => { setDescription(editorState); } const [isError, setError] = useState(null); const addDetails = async (event) => { try { event.preventDefault(); event.persist(); if(userInfo.description.value.length < 50){ setError('Required, Add description minimum length 50 characters'); return; } axios.post(`http://localhost:8080/addArticle`, { title: userInfo.title, description: userInfo.description.value }) .then(res => { if(res.data.success === true){ history.push('/'); } }) } catch (error) { throw error;} } return ( <> <div className="App"> <div className="container"> <div className="row"> <form onSubmit={addDetails} className="update__forms"> <h3 className="myaccount-content"> Add </h3> <div className="form-row"> <div className="form-group col-md-12"> <label className="font-weight-bold"> Title <span className="required"> * </span> </label> <input type="text" name="title" value={userInfo.title} onChange={onChangeValue} className="form-control" placeholder="Title" required /> </div> <div className="form-group col-md-12 editor"> <label className="font-weight-bold"> Description <span className="required"> * </span> </label> <Editor editorState={description} toolbarClassName="toolbarClassName" wrapperClassName="wrapperClassName" editorClassName="editorClassName" onEditorStateChange={onEditorStateChange} /> <textarea style={{display:'none'}} disabled ref={(val) => userInfo.description = val} value={draftToHtml(convertToRaw(description.getCurrentContent())) } /> </div> {isError !== null && <div className="errors"> {isError} </div>} <div className="form-group col-sm-12 text-right"> <button type="submit" className="btn btn__theme"> Submit </button> </div> </div> </form> </div> </div> </div> </> ) } export default Add
components/Edit.js
import React,{useState,useEffect} from 'react'; import axios from 'axios'; import Editpost from './Editpost'; const Edit = (props) => { useEffect(() => { viewPostId(props.match.params.postID); }, []); const [ispostId, setpostId] = useState([]); const viewPostId = async(ids) =>{ try { await axios.post(`http://localhost:8080/getPostId`,{ ids: props.match.params.postID }) .then(res => { if(res.data.success === true){ setpostId(res.data.listId); } }) } catch (error) { throw error;} } return ( <> {ispostId.length > 0 ? <> <Editpost postList={ispostId} editPostID={props.match.params.postID} /> </> : null } </> ) } export default Edit
components/Editpost.js
import React,{useState} from 'react'; import { EditorState, convertToRaw, ContentState,convertFromHTML } from 'draft-js'; import { Editor } from 'react-draft-wysiwyg'; import draftToHtml from 'draftjs-to-html'; import { useHistory } from "react-router-dom"; import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; import axios from 'axios'; function Editpost(props) { let history = useHistory(); const [userInfo, setuserInfo] = useState({ title: props.postList[0].title, }); const onChangeValue = (e) => { setuserInfo({ ...userInfo, [e.target.name]:e.target.value }); } let editorState = EditorState.createWithContent( ContentState.createFromBlockArray( convertFromHTML(props.postList[0].description) )); const [description, setDescription] = useState(editorState); const onEditorStateChange = (editorState) => { setDescription(editorState); } const [isError, setError] = useState(null); const PoemAddbooks = async (event) => { try { event.preventDefault(); event.persist(); if(userInfo.description.value.length < 50){ setError('Required, Add description minimum length 50 characters'); return; } axios.post(`http://localhost:8080/editArticle`, { title: userInfo.title, description: userInfo.description.value, ids:props.editPostID }) .then(res => { // then print response status if(res.data.success === true){ history.push('/'); } }) } catch (error) { throw error;} } return ( <div className="App"> <div className="container"> <div className="row"> <form onSubmit={PoemAddbooks} className="update__forms"> <h3 className="myaccount-content"> Edit </h3> <div className="form-row"> <div className="form-group col-md-12"> <label className="font-weight-bold"> Title <span className="required"> * </span> </label> <input type="text" name="title" value={userInfo.title} onChange={onChangeValue} className="form-control" placeholder="Title" required /> </div> <div className="form-group col-md-12 editor"> <label className="font-weight-bold"> Description <span className="required"> * </span> </label> <Editor editorState={description} toolbarClassName="toolbarClassName" wrapperClassName="wrapperClassName" editorClassName="editorClassName" onEditorStateChange={onEditorStateChange} /> <textarea style={{display:'none'}} disabled ref={(val) => userInfo.description = val} value={draftToHtml(convertToRaw(description.getCurrentContent())) } /> </div> {isError !== null && <div className="errors"> {isError} </div>} <div className="form-group col-sm-12 text-right"> <button type="submit" className="btn btn__theme"> Submit </button> </div> </div> </form> </div> </div> </div> ) } export default Editpost

MySQL database

In the following example, we will start how to React wysiwyg text editor into the MySQL database using Node js.

  • Database Name – codeat21
  • Table Name – posts
codeat21
CREATE TABLE `posts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `description` longtext COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Next, Node js project structure your folder as follows.

├── config
|  ├── database.js	
├── index.js
├── package.js
└── package-lock.js 
database.js
const util = require('util'); const mysql = require('mysql2'); const pool = mysql.createPool({ connectionLimit: 10, host : 'localhost', user : 'root', password : '', database : 'codeat21' }); // Ping database to check for common exception errors. pool.getConnection((err, connection) => { if (err) { if (err.code === 'PROTOCOL_CONNECTION_LOST') { console.error('Database connection was closed.'); } if (err.code === 'ER_CON_COUNT_ERROR') { console.error('Database has too many connections.'); } if (err.code === 'ECONNREFUSED') { console.error('Database connection was refused.'); } } if (connection) connection.release(); return; }); // Promisify for Node.js async/await. pool.query = util.promisify(pool.query); module.exports = pool;
index.js
const express = require('express'); const app = express(); const path = require('path'); const cors = require('cors'); const bodyParser = require('body-parser'); const port = process.env.PORT || 8080; // Databse Connection const db_connection = require('./config/database').promise(); app.use(cors()); app.use(bodyParser.json() ); app.use(bodyParser.urlencoded({extended:true})); app.get('/allPost', async (req, res) => { try { const [rows] = await db_connection.execute("SELECT * FROM posts "); return res.json({ success: true, listall:rows, }); } catch (err) {console.log(err)} }); app.post('/addArticle', async (req, res) => { try { const [rows] = await db_connection.execute("INSERT INTO `posts` (`title`,`description`) VALUES(?, ?)",[req.body.title,req.body.description]); if (rows.affectedRows === 1) { return res.json({ success: true}) } } catch (err) {console.log(err)} }); app.post('/getPostId', async (req, res) => { try { const [rows] = await db_connection.execute("SELECT * FROM posts where id = ? ",[req.body.ids]); if(rows.length > 0 ){ return res.json({ success: true, listId:rows,}) } } catch (err) {console.log(err)} }); app.post('/editArticle', async (req, res) => { try { const [update] = await db_connection.execute("UPDATE `posts` SET `title`=?, `description`=? WHERE id = ?",[req.body.title,req.body.description,req.body.ids]); if (update.affectedRows === 1) { return res.json({ success: true, }) } } catch (err) {console.log(err)} }); app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))