Josh Simon
Josh Simon

Reputation: 259

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create: 1.) User double clicks on text 2.) Text turns into input field where user can edit text 3.) User hits enter, and upon submission, text is updated to be edited text. Basically, it's just an edit function where the user can change certain blocks of text.

So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?

My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:

App.js

import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'

const jobItems= [
  {
      id:8,
      chore: 'wash dishes'
  },
  {
      id:9,
      chore: 'do laundry'
  },
  {
      id:10,
      chore: 'clean bathroom'
  }

]

function App() {

  const [listOfTasks, setTasks] = useState(jobItems)

  const updateHandler = (task) => {
    setTasks(listOfTasks.map(item => {
      if(item.id === task.id) {
        return {
          ...item,
          chore: task.chore
        }
      } else {
        return task
      }
    }))
  }

const cardComponents = cardData.map(card => {
  return <Card key = {card.id} name = {card.name}/>
})

return (
  <div>
    <Header/>
    <Dates/>
    <div className = 'card-container'>
    {cardComponents}
    </div>
    <TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
    <div>
      <Footer/>
    </div>
</div>
)
}

export default App;

Tasks.jsx

import React, {useState} from 'react'

function Tasks (props) {

const [isEditing, setIsEditing] = useState(false)



    return(
        <div className = 'tasks-container'>
            {
                isEditing ? 
                <form>
                <input type = 'text' defaultValue = {props.item.chore}/> 
                </form>
                : <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
            }
        </div>
        
    )
}

export default Tasks

TaskList.jsx

import React from 'react'
import Tasks from './Tasks'



function TaskList (props) {

    const settingTasks = props.setTasks //might need 'this'

    return (
        <div>
            {
                props.jobItems.map(item => {
                    return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
                })
            }
        </div>
    )
}

export default TaskList

Upvotes: 6

Views: 25180

Answers (4)

Brian Min
Brian Min

Reputation: 305

react-edit-text is a package I created which does exactly what you described.

It provides a lightweight editable text component in React.

A live demo is also available.

Upvotes: 1

Pavlos Karalis
Pavlos Karalis

Reputation: 2966

See sandbox for working example:

https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js

Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:

const handleKeyPress = (e) => {
    if (e.key === "Enter") {
      setIsEditing(false);
    }
  };

Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:

const updateHandler = (e, index) => {
    const value = e.target.value;
    setTasks(state => [
      ...state.slice(0, index),
      { ...state[index], chore: value },
      ...state.slice(index + 1)
    ]);
  };

Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.

Upvotes: 3

Thirumani guhan
Thirumani guhan

Reputation: 398

You forgot onChange handler on input element to set item's chore value.

Tasks.jsx must be like below

import React, {useState} from 'react'

function Tasks (props) {

const [isEditing, setIsEditing] = useState(false)

const handleInputChange = (e)=>{
    // console.log( e.target.value );
    // your awesome stuffs goes here
}

    return(
        <div className = 'tasks-container'>
            {
                isEditing ? 
                <form>
                <input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/> 
                </form>
                : <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
            }
        </div>
        
    )
}

export default Tasks

Upvotes: 4

M-N
M-N

Reputation: 633

So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:

function Tasks ({item}) {
    return(
        <div className = 'tasks-container'>
            <div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
                {item.chore}
            </div>
        </div> 
    )
}

Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.

Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.

Upvotes: 3

Related Questions