Send Draft.js data to a server and fetch it with React hooks

JB
JavaScript in Plain English
6 min readFeb 10, 2021

--

This tutorial will walk through the steps for sending Draft.js data to a server using a POST request, and then how to retrieve and display that data using a GET request.

I’m using an Express backend, a MongoDB database, and a React frontend. This will assume some basic knowledge of Draft.js—there are a lot great setup guides out there.

Dependencies: react , axios , draft-js

Structure:

  • AddPost component with Draft.js Editor → useSubmit() hook to handle the POST request.
  • PostGallery component to display posts → usePosts() hook to handle fetching the posts.

Pre-requisite setting up the GET and POST routes

First, make sure that you have an established route that can accept a POST request. I’m going to be sending notes from Draft.js so my route is called /posts. Explaining how to set up a backend is out of the scope of this article, but the schemas, routes, and controllers are all in the repo.

Express users: make sure you have the body parsing middleware set up before you make POST requests:

app.use(express.json());app.use(express.urlencoded({extended: true}));

The top line will allow you send JSON objects in the request body, and the bottom line will allow you to send strings, arrays, and nested objects.

1. POST Draft.js data

Send Draft.js data to a server with a POST request

I want to POST my data when I click a save button on my React interface. I’m going to attach a function to that button that will send a POST request. I’m going to save it onSubmit in its parent component.

import React from 'react';
import RichEditor from '../components/editor/RichEditor';
import Button from '../components/Button';
import useSubmit from '../hooks/useSubmit';
import useRichEditor from '../hooks/useRichEditor';
const Add = () => {const {
editorState,
onNoteChange,
clearEditor,
mapKeyToEditorCommand,
handleKeyCommand,
toggleInlineStyle,
toggleBlockType
} = useRichEditor();
const { submitPage } = useSubmit();const handleSubmit = async (e) => {
e.preventDefault();
const data = editorState.getCurrentContent();
await submitPage(data);
};
return (
<form handleSubmit={handleSubmit}>
<RichEditor
editorState={editorState}
onNoteChange={onNoteChange}
clearEditor={clearEditor}
mapKeyToEditorCommand={mapKeyToEditorCommand}
handleKeyCommand={handleKeyCommand}
toggleInlineStyle={toggleInlineStyle}
toggleBlockType={toggleBlockType}
/>
<Button kind="primary" type="submit" label="add"/>
</form>
);
}
export default Add;

On submit, we call e.preventDefault() to prevent the page from refreshing, and then get all of the current content in the editor with editorState.getCurrentContent() . Then we call the handleSubmit function in a custom hook.

Now let’s write the function that we attached to our save button, handleSave() This is the part where we actually send our data. I’m using Axios for all of my API calls. Axios is a library for making HTTP requests; this is a good primer on using it with React.

import { useState } from 'react';
import axios from 'axios';
import { convertToRaw } from 'draft-js';
export default function useSubmit() {const submitPage = async (data) => {
const content = JSON.stringify(convertToRaw(data));

return axios.post('/posts', {
content
}).catch(err => console.log(err));
}
return {
submitPage
}
}

Let’s break this POST request into pieces:

  1. I call the built-in Draft.js method getCurrentContent() on the editorState. This method returns the currentState object, which contains the text and all of its styling.
  2. I use the build-in Draft.js method convertToRaw() , which converts the content state to a JS object that contains the content blocks. On top of that I use the method JSON.stringify() , which makes the JS object into a JSON string. As a string, it is now storable.
  3. I set up my axios request. These can be formatted a few ways; I prefer to use this object-like format for POST requests. I specify the method, the url and endpoint, data, and headers. Note that my data object is formatted like this:
{ 
content
}

Because it mirrors how my data object is set up in the createNote() API call in Express:

res.status(201).json({
status: 'success',
data: {
content: newNote
}
})

These have to correspond so the data can route correctly!

Let’s get some data for our POST request. I’m going to go into my Draft.js editor and write some sample data to send:

I’m going to add some formatting to this note so I make sure my formatting is retained when I POST this data and then GET it later. I’m going to add some bold text and a bulleted list:

Inline style: bold, block style: unordered list

When the note is posted, it also appears in my database like this:

content: {"blocks":
[
{"key":"4rh99","text":"It's very gray today. I ate cornflakes ","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},
{"key":"fdh6","text":"honey","type":"unordered-list-item","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"13aee","text":"a banana","type":"unordered-list-item","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"bb1p5","text":"and peanutbutter ","type":"unordered-list-item","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"8j2ad","text":"for breakfast. ","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"fjio6","text":"","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"eblll","text":"Valentine's day is this weekend. ","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":16,"style":"BOLD"}],"entityRanges":[],"data":{}}],"entityMap":{}}

This the block map, which delineates the different content blocks, which are like paragraphs. If you read through this, it shows a bit how Draft.js styles and stores data.

2. GET Draft.js data

Displaying Draft.js data on the frontend is not so straightforward as fetching the data and mapping it onto a component. But it is not difficult.

First, we retrieve the Draft.js data from our database using a GET request and the /posts route we created in our API earlier.

This is the custom hook to GET the posts that will be used with the PostGallery component to display the posts.

> usePosts.js

import { useState } from 'react';
import axios from 'axios';
import { convertToRaw } from 'draft-js';
export default function usePosts() {const [posts, setPosts] = useState(null);const getPosts = async (page, add) => {
return axios.get(`/posts/`)
.then(res => {
const { docs } = res.data.data;
setPosts(docs);
}).catch(err => console.log(err));
}
return {
posts,
getPosts
}
}

This GET request fetches the data stored at the /posts route, which are all the notes, and set these documents as the posts state. But remember, these content of these are stored in an object. In the object, the content is a JSON string that contains the note’s content blocks.

In the post gallery, the content must be appropriately parsed. We are going to display the posts in the Draft.js Editor object—the same one used to write the post, but with the property, readOnly , set so the displayed note cannot be edited.

Using the Editor to display the note, in addition to write the note, has several advantages:

  • Easier to display all note’s styles.
  • The readOnly property can be toggled to false if the post is editable after it’s displayed.

> PostGallery.js

import React, { useEffect } from 'react';
import { Editor, EditorState, convertFromRaw } from 'draft-js';
import usePosts from '../hooks/usePosts';
const PostGallery = () => {const { posts, getPosts } = usePosts();useEffect(() => {
getPosts();
}, []);
return(
<div className="posts">
{pages && pages.map(el => {
const contentState = convertFromRaw(JSON.parse(el.content));
const editorState = EditorState.createWithContent(contentState);

return(
<div style={{paddingTop:'3rem'}}>
<Editor editorState={editorState} readOnly={true}
/>
</div>
)
})}
</div>
)
};
export default PostGallery;

This component is:

  • Calling the function that makes the GET request in the custom hook in the useEffect hook.
  • Usingel.content to get the note’s content from the note object and JSON.parse to convert it back to an object from its stringified state. Then applyconvertFromRaw , a Draft.js function that converts the object with content blocks back into readable text.
  • Initializing a Draft.js Editor, and setting that parsed content as the Editor’s initial editorState.
  • Setting the Editor to readOnly, as discussed, so it cannot be edited.

It will appear on the frontend as a text block (uneditable), and with the rich styling preserved. If you save other attributes, like post author, creation date, etc. they can easily be styling to appear here too.

That’s all there is to POSTing and GETing Draft.js data.

Summary

POST

  • Get current content from the editor state using editorState.getCurrentContent() and save that to a variable.
  • Convert the current state to a string with these methods: JSON.stringify(convertToRaw(data));

GET

  • Display the data in a Draft.js Editor set to readOnly
  • Fetch the post from the server and then convert it to readable content with this.
    const contentState = convertFromRaw(JSON.parse(el.content));
const editorState = EditorState.createWithContent(contentState);
  • Show it in an Editor like so:
<Editor editorState={editorState} readOnly={true}

--

--