React Double-tap Text to Input.
Tags: react, javascript
Published: 2021-02-12 12:59:06

#Overview
Ever thought of using double tapping on a text to make it an input field to edit the text? 
Well i wanted to do something like that in my React application and I searched but didn't see any solution until i came across this github [gist](https://gist.github.com/c0d0g3n/ed630ea7a24ac8a3c4a09faab77aa7ec) and it worked just fine bar some minor tweaks. So through this article i'll try to explain how it works and some additions I made to it.
>**Why not use onDoubleClick?**
>Well when you fire a double click on an element there is the danger of not being able to to an onClick event on that same element as seen on on this [stackoverflow](https://stackoverflow.com/questions/25777826/onclick-works-but-ondoubleclick-is-ignored-on-react-component).

##Getting Started
As seen on that github gist it'll take just to react components to get this done.
1. EditableContainer, and
2. FieldStyle.
Of course we could name them whatever we want but i'll just stick with that.

###Firstly, EditableContainer class
We'll break the code down into different segments to explain what is going on.
**First we make our imports, initialize our class and render the component (standard).**
import react and the FieldStyle component
```
import React from 'react';
import Field from './FieldStyle';
export default class EditableContainer extends React.Component {
  constructor (props) {
    super(props);
    // initialize the counter for the clicks.
    this.count = 0;

    // initialize the state
    this.state = {
      edit: false,
      value: ''
    }
  }
...
  render () {
    const {doubleClick, handleEnter, children, ...rest} = this.props;
    const {edit, value} = this.state;
    if (edit) {
      // edit mode
      return (
        <Field
          autoFocus
          defaultValue={value}
          onBlur={this.handleBlur.bind(this)}
          onKeyPress={this.handleEnter.bind(this)}
        />
      )
    } else {
      // view mode
      if(doubleClick){
        return (
          <p
            onClick={this.handleDoubleClick.bind(this)}
            {...rest}
          >
            {children}
          </p>
        )
      }else{
        return (
          <p
            onClick={this.handleSingleClick.bind(this)}
            {...rest}
          >
            {children}
          </p>
        )        
      }
    }
  }
}
```
The `doubleClick` prop is for when the parent component what it to either change to an input either after a single click or on double click, `handleEnter` is a callback function from the parent function on how to handle the input value and using it to carryout some operation (sending an asynchronous request to maybe edit something) after editing and exiting the the input field, the `children` is for the text value or maybe another component like an a tag and the `...rest` is for other props like the `className` to be applied to the p tag.
If in edit mode it renders the input field with the value carrying the `onBlur` and `onKeyPress` action handlers making reference to methods we'll explain later, if not in edit mode it check if its a `doubleClick` operation or a single click and applies the appropriate `onClick` event handler.

**getDerivedStateFromProps()**
```
...
static getDerivedStateFromProps(props, state){
    if(props.edit){
      return { edit: props.edit };
    }
    return null;
  }
...
```
The react component lifecycle method `getDerivedStateFromProps` that gets called with every change in props right before the render method is called. [Further Reading](https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops)
This function is to set an option to make the component editable at initialization by the parent component.


**handleDoubleClick()**
```
...
  handleDoubleClick (e) {
    // cancel previous callback
    if (this.timeout) clearTimeout(this.timeout);

    // increment count
    this.count++;
  
    // schedule new callback  [timeBetweenClicks] ms after last click
    this.timeout = setTimeout(() => {
      // listen for double clicks
      if (this.count === 2) {
        // turn on edit mode
        this.setState({
          edit: true,
          value: e.target.textContent
        })
      }

      // reset count
      this.count = 0
    }, 250) // 250 ms
    //}, settings.timeBetweenClicks) // 250 ms
  }
...
```
This function is where the magic happens :smile:.
First of it clears the previous callback on the timeout property, then it increments the click count. After that it create a new instance of the timeout and inside that callback it checks if the number of clicks is 2 signalling that there has been a double click in the specified time (the time there is 250ms of course you can change that, but it has to be reasonable because we don't want it take to long between clicks and it shouldn't be to short for it to be impossible to do the double click either).

**handleSingleClick()**
```
...
handleSingleClick (e) {
    this.setState({
      edit: true,
    });
  }
...
```
This function is as simple as it appears once it's clicked it sets it to edit mode to make the input field appear.

**handleBlur()**
```
...
handleBlur (e) {
    // handle saving here, as we'll see in handle enter, I did't want to do that here in situations where the user mistakenly loses focus on the input field.
    
    // close edit mode
    this.setState({
      edit: false,
      value: e.target.value
    });
  }
...
```
This function takes care of the event `onBlur` which happens when the user loses focus on the input, so we want to exit the edit mode and display the newly typed value. As I said in that comment, I did't think it wise to save the input value `onBlur` to prevent saving values when the user didn't intend to do that.

**handleEnter()**
````
...
handleEnter(e){
    if(e.code === "Enter" || e.charCode === 13 || e.which === 13){
      this.props.handleEnter(e.target.value);

      this.setState({
        edit: false,
        value: ''
      });
    }
  }
...
```
This function is to check when the user uses the `enter`:leftwards_arrow_with_hook: key or if the user on mobile it'll check its equivalent to send it to the parent component to do as it please with it (make an update operation asynchronously with it) then exit edit mode and clear the input value.
In hindsight the name might have been different but for its current purpose it'll do, but if we'll like to exit edit mode my say using the `esc` key we could change the name and check for that, but for now this will do.
..Putting all together..
```
import React from 'react';
//import settings from '../settings.js'
import Field from './FieldStyle';

export default class EditableContainer extends React.Component {
  constructor (props) {
    super(props);
    // init counter
    this.count = 0;

    // init state
    this.state = {
      edit: false,
      value: ''
    }
  }
  static getDerivedStateFromProps(props, state){
      //console.log(props.lists);
    if(props.edit){
      return { edit: props.edit };
    }
    return null;
  }

  componentWillUnmount () {
    // cancel click callback
    if (this.timeout) clearTimeout(this.timeout);
  }

  handleDoubleClick (e) {
    // cancel previous callback
    if (this.timeout) clearTimeout(this.timeout);

    // increment count
    this.count++;
  
    // schedule new callback  [timeBetweenClicks] ms after last click
    this.timeout = setTimeout(() => {
      // listen for double clicks
      if (this.count === 2) {
        // turn on edit mode
        this.setState({
          edit: true,
          value: e.target.textContent
        })
      }

      // reset count
      this.count = 0
    }, 250) // 250 ms
    //}, settings.timeBetweenClicks) // 250 ms
  }

  handleSingleClick (e) {
    this.setState({
      edit: true,
    });
  }

  handleBlur (e) {
    // handle saving here
    
    // close edit mode
    this.setState({
      edit: false,
      value: e.target.value
    });
  }
  handleEnter(e){
    if(e.code === "Enter" || e.charCode === 13 || e.which === 13){
      this.props.handleEnter(e.target.value);

      this.setState({
        edit: false,
        value: ''
      });
    }
  }

  render () {
    const {doubleClick, handleEnter, children, ...rest} = this.props;
    const {edit, value} = this.state;
    if (edit) {
      // edit mode
      return (
        <Field
          autoFocus
          defaultValue={value}
          onBlur={this.handleBlur.bind(this)}
          onKeyPress={this.handleEnter.bind(this)}
        />
      )
    } else {
      // view mode
      if(doubleClick){
        return (
          <p
            onClick={this.handleDoubleClick.bind(this)}
            {...rest}
          >
            {children}
          </p>
        )
      }else{
        return (
          <p
            onClick={this.handleSingleClick.bind(this)}
            {...rest}
          >
            {children}
          </p>
        )        
      }
    }
  }
}
```
###FieldStyle class
This class is more straight forward than the EditableContainer class
```
import React from 'react'

export default class FieldStyle extends React.Component {
  componentDidMount () {
    this.ref && this.ref.focus()
  }

  render () {
    const {autoFocus, ...rest} = this.props

    // auto focus
    const ref = autoFocus ? (ref) => { this.ref = ref } : null
    return (
      <input
        ref={ref}
        type="text"
        {...rest}
      />
    )
  }
}
```
The `componentDidMount` function would run when the component has been mounted.
`this.ref && this.ref.focus()`
Using this line of code we can check if the component has a ref and then we'll focus on it. In the `render()` method we first check if the `autofocus` prop is true then we ill create a ref on it to do the focusing as shown above, then the input is rendered.
##Putting Our Component to Use
```
import React from 'react';
import EditableContainer from './EditableContainer';

const App = () => {
  const handleSingleTap(text){
    //carry out what ever we want to do with the text.
  }
  const handleDoubleTap(text){
    //carry out what ever we want to do with the text.
  }
  return(
    <div>
      <EditableContainer
        doubleClick={false}
        handleEnter={handleSingleTap}
        className='What-Ever-Classname'>
        Single tap to edit me!!
      </EditableContainer>    
      <EditableContainer
        doubleClick={true}
        handleEnter={handleDoubleTap}
        className='What-Ever-Classname'>
        Double tap to edit me!!
      </EditableContainer>
    </div>
  ) 
}

export default App
```
Full implementation could be found [here](https://gist.github.com/Otagera/c491622d8a60e3a7c5a4baa6c4cffd27).
##Finally
There is the npm [package](https://www.npmjs.com/package/react-editext) which is great for editing component but it uses a button which would not work for double click. I hope to try my hand in open source (first time :sweat_smile:) and see if I can add this feature to the package so fingers crossed :v: