5
votes

I am currently creating a React todo application. So basically I have two component TodoList and TodoItem

TodoList component will receive an array of objects consisting title as the property and map through this with TodoItem component

In my TodoItem component, the user can choose to edit or delete the item. If the user chose to edit, a modal will show up with the existing title using a textarea. However, I have trouble implementing this function as the modal will always show up with the last element of the array.

TodoList Component

import React, { Component } from 'react'
import TodoItem from './TodoItem'
import { connect } from 'react-redux'
import { clear_todo } from '../store/actions/todoActions'

class Todolist extends Component {

  clearList = (e) => {
    e.preventDefault()
    this.props.clearList(clear_todo());
  }

  handleChange = (index, title) => {
    this.setState({
      [index]: title
    })
  }

  render() {
    const { items, editItem } = this.props.todo
    return (
      <ul className="list-group my-5">
        <h3 className="text-capitalize text-center">
          Todo List
        </h3>
        {
          items.map((item, index) => {
            const editedTitle = item.title

            return (<TodoItem key={item.id} title={item.title}
              id={item.id}
              editedTitle={editedTitle}
              onChange={this.handleChange}
              editItem={editItem} />)
          })
        }

        <button className="btn btn-danger btn-block text-capitalize mt-5" onClick={this.clearList}> Clear List </button>
      </ul>
    )
  }
}

const mapStateToProps = state => {
  return {
    todo: state.todo
  }
}

const mapDispatchToProps = dispatch => {
  return {
    clearList: (clear_todo) => { dispatch(clear_todo) }
  }
}


export default connect(mapStateToProps, mapDispatchToProps)(Todolist)

TodoItem Component

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { delete_todo, edit_todo, toggle_edit } from '../store/actions/todoActions'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap';
import TodoEditItem from './TodoEditItem'

class Todoitem extends Component {

  // constructor(props) {
  //   super(props)
  //   this.state = {
  //     [props.id]: props.title
  //   }
  // }


  handleEdit = (id, title) => {
    this.props.editTodo(edit_todo(id, title))
  }

  toggleEdit = (editItem, title) => {
    this.props.toggleEdit(toggle_edit(!editItem))
    // this.initializeTitle(title)

  }

  handleDelete = (id) => {
    this.props.deleteTodo(delete_todo(id))
  }

  // onChange = (e, id) => {
  //   this.setState({
  //     [id]: e.target.value
  //   })
  // }

  componentDidMount() {
    // console.log(this.props)
    // this.initializeTitle(this.props.title)
    this.setState({
      [this.props.id]: this.props.editedTitle
    })
    console.log(this.state)
  }


  render() {
    // console.log(this.state)
    let { id, title, editItem, editedTitle, index } = this.props
    console.log(id)
    // console.log(index)
    // let { item } = this.state

    return (
      <div>
        <li className="list-group-item text-capitlize d-flex justify-content-between my-2">
          <h6>{title}</h6>
          <div className="todo-icon">
            <span className="mx-2 text-success" onClick={this.toggleEdit.bind(this, editItem)} >
              <i className="fas fa-pen"></i>
            </span>
            <span className="mx-2 text-danger" onClick={this.handleDelete.bind(this, id)}>
              <i className="fas fa-trash"></i>
            </span>
          </div>

          <Modal isOpen={editItem}>
            <ModalHeader>Edit Todo Item</ModalHeader>
            <ModalBody>
              <Form>
                <FormGroup row>
                  <Input type="textarea" name="text" value={this.state ? this.state[id] : ""} onChange={this.props.onChange} />
                </FormGroup>
              </Form>
            </ModalBody>
            <ModalFooter>
              <Button color="primary" onClick={this.handleEdit.bind(this, id, editedTitle)}>Save</Button>{' '}
              <Button color="secondary" onClick={this.toggleEdit.bind(this, editItem)}> Cancel</Button>
            </ModalFooter>
          </Modal>
        </li>
        {/* {editItem ? <TodoEditItem title={title} editItem={editItem} /> : ''} */}
      </div>

    )
  }
}


const mapDispatchToProps = dispatch => {
  return {
    deleteTodo: (delete_todo) => { dispatch(delete_todo) },
    editTodo: (edit_todo) => { dispatch(edit_todo) },
    toggleEdit: (toggle_edit) => { dispatch(toggle_edit) },
  }
}


export default connect(null, mapDispatchToProps)(Todoitem)

Sample Image

Image of TodoItem

Image of Modal

As you can see from the image of TodoItem, I am trying to edit "Take out the garbage" but my textarea has been prepopulated as the last element.

1
A runnable sample on codesandbox would get you helped more easilyYik San Chan
Can you check your actual rendered DOM to see if there is just a single modal or if there is one for each item you map? I suspect your code may somehow reuse a single modal and the last element you map is the last to set it.Drew Reese
You're using the same editItem flag passed through props to determine if both modals should open. I'm guessing both modals are being opened on top of one another. You can check by console logging inside the componendDidMount and see if it prints twiceBrian Thompson
@DrewReese is right, your Modal can be reused, why have you created a editTodo action ? the editTodo method can be created in the parent component to set a state and that state should be passed to the child component.Muhammad Zia

1 Answers

5
votes

You're using the same variable to determine all of the TodoItem's open state. The result is it appears that it is only taking the last value from the array, but really each modal is opening at once and the last one is the only visible modal.

// editItem here is used to determine the open state of both modals
const { items, editItem } = this.props.todo 
...
{
  items.map((item, index) => {
    const editedTitle = item.title
    return (
      <TodoItem 
        key={item.id} 
        title={item.title}
        id={item.id}
        editedTitle={editedTitle}
        onChange={this.handleChange}
        editItem={editItem}            // Same value
      />
    )
  })
}

...

let { editItem } = this.props
<Modal isOpen={editItem}>             // All will open and close at the same time

Instead, use a different flag, or just manage the open state in each child like this:

<span className="mx-2 text-success" onClick={() => this.setState({open: true})} >
  <i className="fas fa-pen"></i>
</span>

...

<Modal isOpen={this.state.open}>