Paramanantham Harrison Web and mobile app developer. Love exploring the depth of JS full-stack development. React, Vue, React Native, Next JS, GraphQL are my current love interest. https://learnwithparam.com

The complete guide to building inline editable UI in React

6 min read 1813

Introduction

UI for web applications are becoming increasingly complex by the day. With more powerful client side libraries, we can push the limits of UX through UI experimentation.

One of these experiments involves inline editable UI. Most modern web apps have inline editable UI.

A gif showing editable UI in React.
Basic inline editable UI

The following are some of the most prominent products currently using inline editable UI with clear UX:

  • Asana – One of the best custom UIs with inline editable tables and forms
  • Google sheets – Inline editable smart table UI in web
  • Trello – Trello cards can be edited by clicking on them
  • Airtable and Notion are some other modern apps that heavily use inline editable UI
Google sheets UI

In this post, we’re going to learn how to create basic inline editable UI in React through the simplified version of Asana create task, with only task name and description field.

You can check out a demo of what we’re going to build here.

Before going deep into our custom implementation of inline editable components, let’s talk about all the existing React components we can use to achieve different inline editable UI.

Existing React libraries

Not many libraries exist for inline editable UI. The main reason for this is that it’s very difficult to satisfy different user needs and demands with inline editable components.

Some packages that have been used for inline editable components (but not updated often) include the following:

React inline editinghttps://github.com/bfischer/react-inline-editing
React edit inlinehttps://github.com/kaivi/ReactInlineEdit

These two packages are very simple and allow users to edit a label as an input box. You can customize the style based on your needs.

You can use it with any table-like structure to create an inline editable table.

React content editable – https://github.com/lovasoa/react-contenteditable

This is the most famous package for inline editable UI.

The main difference between this component and others is that it allows you to inline edit HTML–not just text content.

This can be used for markup and markdown editing in the UI.

You can also manage the height of the editable element to create the look and feel of an input or a textarea element. Check out an example in codesandbox.

React tablehttps://github.com/tannerlinsley/react-table

React table is one of the most popular table libraries that also allows you to edit inline. You can create a UI like Google Sheets by customizing these library components.

Check out their kitchen sink for how this editable UI works in the table layout.

How editable UI works

First, let’s see how an editable UI works:

  • An editable UI will simply display a label
  • On hover, it will show the borders to make the UI look inline editable
  • On click, it will transform the simple label to a custom input element. Some of the notable input elements are input fields, textarea, select component, date picker, etc.
  • By clicking Enter or Escape, we’ll go back to the initial state and show the label.
  • If you click Tab, it will once again switch to an input element and make the component accessible using the keyboard.

We are going to build a simple version without the Tab functionality.

You can easily add the Tab functionality, but I left that as an exercise for you. You can create a pull request to my repo here.

Building a simple React component for editable UI

First, create a simple React app using create-react-app.

I’m using tailwindcss for styling. You can check out this blog for more details on how to configure tailwind with create-react-app.

Let’s create the Editable React component:

// Editable.js
import React, { useState } from "react";

// Component accept text, placeholder values and also pass what type of Input - input, textarea so that we can use it for styling accordingly
const Editable = ({
  text,
  type,
  placeholder,
  children,
  ...props
}) => {
  // Manage the state whether to show the label or the input box. By default, label will be shown.
// Exercise: It can be made dynamic by accepting initial state as props outside the component 
  const [isEditing, setEditing] = useState(false);

// Event handler while pressing any key while editing
  const handleKeyDown = (event, type) => {
    // Handle when key is pressed
  };

/*
- It will display a label is `isEditing` is false
- It will display the children (input or textarea) if `isEditing` is true
- when input `onBlur`, we will set the default non edit mode
Note: For simplicity purpose, I removed all the classnames, you can check the repo for CSS styles
*/
  return (
    <section {...props}>
      {isEditing ? (
        <div
          onBlur={() => setEditing(false)}
          onKeyDown={e => handleKeyDown(e, type)}
        >
          {children}
        </div>
      ) : (
        <div
          onClick={() => setEditing(true)}
        >
          <span>
            {text || placeholder || "Editable content"}
          </span>
        </div>
      )}
    </section>
  );
};

export default Editable;

The component is very straightforward:

  • If the isEditing state is true, then it displays the children. That’s where we pass the input or textarea elements. The input state will be managed outside of this component.
  • If the isEditing state is false, then we display the simple label text or placeholder depending on whether the text value is empty or not.

Lets see what a simple input editable component looks like:

// App.js - Input editable UI

import React, { useState } from "react";
import Editable from "./Editable";

function App() {
  // State for the input
  const [task, setTask] = useState("");

  /*
    Enclose the input element as the children to the Editable component to make it as inline editable.
  */
  return (
    <Editable
      text={task}
      placeholder="Write a task name"
      type="input"
    >
      <input
        type="text"
        name="task"
        placeholder="Write a task name"
        value={task}
        onChange={e => setTask(e.target.value)}
      />
    </Editable>
  );
}

export default App;

Here we enclose input inside the Editable component. You can enclose any custom form component to make it an editable UI.

This is a pretty simple example–if you want to create a more complex example for editable UI you can create Higher order components or custom Hooks to manage all the states outside the editable component.

Lets see how the editable component works for a textarea:

<Editable
  text={description}
  placeholder="Description for the task"
  type="textarea"
>
  <textarea
    name="description"
    placeholder="Description for the task"
    rows="5"
    value={description}
    onChange={e => setDescription(e.target.value)}
  />
</Editable&gt

It’s that simple. We just swapped the input element with a textarea and it works as long as we provide the proper CSS based on the type we pass the  Editable component.

However, we’ll find we run into a few problems:

  • When we click on the label, it won’t auto-focus on the input element
  • A simple form can be navigated using the Tab key. However, an inline editable UI can’t be navigated without manually implementing that functionality.

Solving the focus issue

In order to solve the focus issue, we need to use a reference to the input element and focus it when the edit state is set.

// App.js
import React, { useRef, useState } from "react";
import Editable from "./Editable";

function App() {
  /* 
    1. create a reference using use reference and add the ref={inputRef} to input element
    2. pass this reference to the Editable component, use different name than ref, I used `childRef`. Its basically a normal prop carrying the input element reference.
  */
  const inputRef = useRef();
  const [task, setTask] = useState("");

  return (
    <Editable
      text={task}
      placeholder="Write a task name"
      childRef={inputRef}
      type="input"
    >
      <input
        ref={inputRef}
        type="text"
        name="task"
        placeholder="Write a task name"
        value={task}
        onChange={e => setTask(e.target.value)}
      />
    </Editable>
  );
}
export default App;

Next, we’ll pass the input element reference to the Editable component, then focus when the isEditing state is true:

// Editable.js
import React, { useState, useEffect } from "react";
import "./Editable.css";

const Editable = ({ childRef, ... }) => {
  const [isEditing, setEditing] = useState(false);

  /* 
    using use effect, when isEditing state is changing, check whether it is set to true, if true, then focus on the reference element
  */ 
  useEffect(() => {
    if (childRef && childRef.current && isEditing === true) {
      childRef.current.focus();
    }
  }, [isEditing, childRef]);

  const handleKeyDown = (event, type) => {
    ...
  };

  return (
    ...
};

export default Editable;

Glitches with keydown events

Here are a few things to be aware of when dealing with keydown events.

For input element:

  • All the keys (Enter, Escape and Tab key) will set the isEditing state to false.

For textarea:

  • Enter key has to add a new line inside textarea, so we need to handle this use case separately.
const handleKeyDown = (event, type) => {
    const { key } = event;
    const keys = ["Escape", "Tab"];
    const enterKey = "Enter";
    const allKeys = [...keys, enterKey]; // All keys array

  /* 
    - For textarea, check only Escape and Tab key and set the state to false
    - For everything else, all three keys will set the state to false
  */
    if (
      (type === "textarea" && keys.indexOf(key) > -1) ||
      (type !== "textarea" && allKeys.indexOf(key) > -1)
    ) {
      setEditing(false);
    }
}:

Exercise: accessibility for forms with Tab key navigation

By default, input and textarea are hidden. As a result, we can’t navigate the form fields just by hitting the Tab key.

In order to achieve keyboard support, we need to monitor the Tab key event on the component or on the whole page and set the state manually to each element. Then we can navigate to the next form element on the next key press.

We didn’t implement this in our example code, but it’s worth a try to make sure you can handle keypress events on a page in React.

When to use inline editable UI

You don’t need to use inline editable UI for most basic form needs.

However, for complex web applications where you have lots of content with edit options, it’s best to build both view and edit in a single place. If you don’t, you’ll have to maintain two different UIs.

Challenges in inline editable UI

The most significant challenges you may run into in inline editable UI involve showing errors. You should account for this when thinking about UX.

You may also have difficulty achieving the level of accessibility necessary to support mouse events. Touch events will likely also be an issue.

Finally, supporting mobile devices can be hard when you have form elements hidden. This will be especially tricky if the UI has to support a mobile layout, because there is no hover to show users whether the field is editable inline on touch screens.

Conclusion

This has been a simple demo and overview about creating inline editable components in React.

Try to use existing components, but if none of the existing ones fit your needs you can create custom ones.

You can check out the demo here and the codebase here.

Share your experience about inline editable UI in the comments!

Plug: , a DVR for web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Paramanantham Harrison Web and mobile app developer. Love exploring the depth of JS full-stack development. React, Vue, React Native, Next JS, GraphQL are my current love interest. https://learnwithparam.com

Leave a Reply