We are going to create a text-editor for react native projects. As you know there are some packages out there but it worth knowing how to create one from scratch.

If you are not sure about using react-native in your mobile projects, you may also read our Why I Chose React over Others article.

Let’s begin

I’m going to use expo to create a new project:

expo init simple-editor
cd simple-editor

We need react-native-webview in this project so let add it first.

expo install react-native-webview

As we want to create our editor inside a WebView, an HTML Page is required. So First we will create a html page and store it as a string variable and then will use it as source of WebView Component.

let’s create a new file named html.ts:

const editorHTML = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        html {
            height: 100%;
            width: 100%;
        }
        body {
            display: flex;
            flex-grow: 1;
            flex-direction: column;
            height: 100%;
            margin: 0;
        }
        #editor {
           flex-grow: 1;
        }
    </style>
</head>
<body>
  <div id="editor" contenteditable></div>
</body>
</html>
`;

export default editorHTML;

As you can see in above code, we just have a div element with id=’editor’ in the body element. We have just added  ‘contenteditable‘ attribute in order to make content of div editable.

Now It’s time to create the Editor component named Editor.tsx as below. It will have a WebView component as text-editor and two buttons to format Bold and Italic.

import React, { Component } from "react";
import { StyleSheet, TouchableOpacity, View, Text } from "react-native";
import EditorHTML from "./html";

export default class Editor extends Component {
  handleFormat(format: string) {
    console.log(format);
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={styles.buttons}>
          <TouchableOpacity
            onPress={() => this.handleFormat("bold")}
            style={styles.btn}
          >
            <Text>B</Text>
          </TouchableOpacity>
          <TouchableOpacity
            onPress={() => this.handleFormat("italic")}
            style={styles.btn}
          >
            <Text>I</Text>
          </TouchableOpacity>
        </View>
        <WebView
         style={styles.editor}
         source={{ html: EditorHTML }}
        />
      </View>
    );
  }
}

let styles = StyleSheet.create({
  editor: {
    flexGrow: 1,
    borderWidth: 1,
  },
  container: {
    margin: 10,
    flexGrow: 1,
    flexDirection: "column",
    alignItems: "stretch",
    justifyContent: "flex-start",
  },
  buttons: {
    marginBottom: 10,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
  },
  btn: {
    alignItems: "center",
    backgroundColor: "#5E8D48",
    padding: 5,
    margin: 3,
    minWidth: 70,
  },
});

Next let update App.tsx:

import React from "react";
import {
  StyleSheet,
  View,
  Platform,
  StatusBar,
  SafeAreaView,
} from "react-native";
import Editor from "./Editor";

const MyStatusBar = ({ backgroundColor, ...props }) => (
  <View style={[styles.statusBar, { backgroundColor }]}>
    <SafeAreaView>
      <StatusBar translucent backgroundColor={backgroundColor} {...props} />
    </SafeAreaView>
  </View>
);

export default function App() {
  return (
    <View style={styles.root}>
      <MyStatusBar backgroundColor="#5E8D48" barStyle="light-content" />
      <Editor />
    </View>
  );
}

const STATUSBAR_HEIGHT = StatusBar.currentHeight;

const styles = StyleSheet.create({
  root: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "stretch",
    justifyContent: "center",
  },
  statusBar: {
    height: STATUSBAR_HEIGHT,
  },
});

If we run the project now, we should be able to write in the editor.

yarn start

How to get messages from RN?

Next to apply formats and send comments to the editor, you need to enable JavaScript on WebView.

javaScriptEnabled={true}

Then Add this script at the end of the body element of html.ts file in order to make sure the enabled JavaScript works.

<script>
  (function(doc) {
    alert('hello editor');
  })(document)
</script>

Now that you find out how to run JavaScript, what we need is to send commands form our RN component. So remove the alert and replace it as below. This will help us get messages from RN Component.

<script>
  (function(doc) {
    var getRequest = function(event) {
      alert(event.data);
    }
    document.addEventListener("message", getRequest , false);
    window.addEventListener("message", getRequest , false);
  })(document)
</script>

How to send commends to html page?

First we need to add a ref attribute to the WebView. Then we will use the ‘postMessage‘ method in our ‘handleClick‘ function like below:

this._webview = React.createRef();
...
render() {
 return () {
  <WebView
    ...
    ref={this._webview}
  />
 }
}
      
handleFormat(format: string) {
    this._webview.current?.postMessage(format);
}

Now If we click on one of the buttons, we will see an alert with ‘bold’ or ‘italic’ messages.

Next change getRequest function like below code so that we will be able to apply formats.

 var getRequest = function(event) {  
      switch (event.data) {
        case 'bold':
          document.execCommand('bold', false, '');
          break;
        case 'italic':
            document.execCommand('italic', false, '');
            break;
        default:
          break;
 }
}

How to get source html from the editor?

To complete our simple editor, let’s add a button to log the content of editor. This button will send ‘html’ command.

First let us add a button to the editor.tsx:

<TouchableOpacity
 onPress={() => this.handleFormat("html")}
 style={styles.btn}
>
 <Text>Source</Text>
</TouchableOpacity>

In order to get data from the WebView component, we should add onMessage to it. For now will add console.log() to it as below:

<WebView
          ref={this._webview}
          style={styles.editor}
          javaScriptEnabled={true}
          source={{ html: EditorHTML }}
          onMessage={(event) => console.log(event.nativeEvent.data)}
        />

Next let add ‘sendMessage’ function to ‘html.ts’ file. We will use this function to send messages from our html page to the editor.tsx file.

var sendMessage = function(message) {
      if(window.ReactNativeWebView)
        window.ReactNativeWebView.postMessage(message);
}

Then add a case to the switch statement of the html.ts file. This will send the inner html of div for Editor Component.

case 'html':
 var editor = document.getElementById('editor');
 sendMessage(editor.innerHTML);
 break;

And that’s it, Congratulation we’ve successfully created a simple text editor.

Source Code

You may find the completed example here: https://github.com/LearnCodeNet/react-native-simple-editor

What’s next?

At the end you may want to add other formats (ex. underline, ordered lists, justify, …) to the editor. If you find this topic exciting, you may also contribute on our open source project: https://github.com/imnapo/react-native-cn-quill.