React create new component instantly

604 views Asked by At

I am wondering if there is any good way to create a React component instantly (not having to wait for the setState to execute a re-render), or any other ideas that could solve the following problem:

Say I am rendering a list of Text components that are all contenteditable <p/>'s that the user can type in. When the user presses Enter, a new Text is created under the current one, and the caret then moves to this new component, allowing the user to type.

I have implemented this and it is working, but adding a new Text to the rendered list with setState() is too slow, and if the user presses Enter and begins typing immediately, some text is added to the wrong node or lost because the re-render does not happen quick enough.

I need the user to be able to press Enter and type immediately without losing any text, and the user should also be able to press Enter many times in quick succession to create multiple new Text components, which also does not work for the same reason.

The solution should not only not lose any of the typed keys, but also display to the user the typed text (and newly created Text components) as quickly as possible.

Here is an extremely contrived example. Go to the end of one of the texts, press Enter and quickly type. You will see that some of the text gets added to the first component instead of the second.

https://stackblitz.com/edit/react-xbr9pf

Example output showing the order in which keys were pressed:

example

Thank you!

2

There are 2 answers

0
Max On BEST ANSWER

There are a few problems with your implementation, including that = this, constantly getting elements by ids instead of by react refs, hacks with textContent = textContent, trying to focus before setState has kicked in and so on

I've done some minimal edits to your example like changing keyup to keydown, using preventDefault and refs instead of finding element by id. It now works as expected

https://stackblitz.com/edit/react-11g7ec

0
Divyam Dhadwal On

Why don't you do something like this:

App.js

import React, { Component } from 'react';
import {Texts} from './Texts';

class App extends Component {

  state = {
    texts: [
      {id: 0, content: 'Hey ! I\'m first textbox'}
    ]
  }

  updateTexts = (id,newContent) => {
    const updatedText = [...this.state.texts]
    updatedText[id].content = newContent
    this.setState({
      texts: updatedText
    })
  }

  handleKeyDown = (e,id) => {
    if(e.key === 'Enter'){
      const newTextBox = [...this.state.texts]
      newTextBox.push({id: id+1, content: ''})
      this.setState({texts: newTextBox})
    }
  }

  render(){

    return(
        <Texts 
          data={this.state.texts} 
          updateFxn = {this.updateTexts} 
          handleKeyDown= {this.handleKeyDown} 
        />
    ) 
  }
}

export default App;

Texts.js

import React from 'react';

export const Texts = ({data,updateFxn, handleKeyDown}) => {
    return(
        <React.Fragment>
            {
                data.map ((item,i) => (
                        <input 
                            key={i} 
                            type="text" 
                            value={item.content} 
                            onChange={(e)=>updateFxn(item.id,e.target.value)} 
                            onKeyDown={(e)=> handleKeyDown(e,item.id)} 
                        />
                    )
                )
            }

        </React.Fragment>
        )
}

You can just add a basic logic for adding focus to the new element we have created/ simply to the last element of this state array. Rest everything should work just fine.

Also, you can just create another field in updateTexts functions (like a type) and then you can use that type to either add new elements (like we are doing right now) or to even remove the items from the array too.

Something like :

updateTexts = (id,type,newContent) => {
    if(type===1){
      const updatedText = [...this.state.texts]
      updatedText[id].content = newContent
      this.setState({
        texts: updatedText
      })
    }else if(type==0){
      //Logic to remove the element
    }

  }