0

I have a names list which contains 3 names. On clicking any 1, you can edit and save. That value updates to the list. Now I can add how many ever names I want but I am not able to delete/remove any.

This is how it looks in the beginning

On clicking a name (the whole container) it looks like this

I am able to add the new name back to the div like this

And I can add as many as I want, like this like this

Now I want to be able to click on the cross icon and remove the whole element I want. It should go be gone from the page. The other elements should take its place from top to bottom. Remove/delete functionality should be on input and the div with names in it.

Names Component (Names are extracted from this to the one below)

import Sukhdev from '../../../src/components/sukhdev';
import React from 'react';

export default { title: 'Sukhdev' };

const names = [{
    firstName: "Mahatma",
    lastName: "Gandhi"
}, {
    firstName: "Shivaji",
    lastName: "Maharaj"
}, {
    firstName: "Bhagat",
    lastName: "Singh"
},
]

export const sukhdev = () => {
    return(
    <Sukhdev names={names}/>
    ) 
}

Parent Component

import React, { Component } from 'react';
import FirstName from './firstName';
import LastName from './lastName';
import TextArea from './textArea'
import styles from './styles';


export default class Sukhdev extends Component {
  constructor(props) {
    super(props);
      const {names} = this.props;
      const updatedNames = names.map((name) => ({...name, ...{isEditable: false}})); 
      this.state = {
        userNames: updatedNames
      }
  }

 inputNamesHandler = (namesIndex) => {
    const updatedUserNameDetails = [...this.state.userNames];
    updatedUserNameDetails[namesIndex].isEditable = true;
    this.setState({userNames: updatedUserNameDetails})
  }

  saveButton = (inputValue, index) => {
    const {userNames} = this.state; 
    const newNames = [...userNames];
    newNames[index] = {...newNames[index], isEditable: false, firstName: inputValue, lastName: ''};
    this.setState({
      userNames: newNames
    })
  }

  addChild = () => {
    const createInputs = [...this.state.userNames];
    createInputs.push({firstName: '', lastName: '', isEditable: true});
    this.setState({
      userNames: createInputs
    })
  }

   ------> // This is where the changes need to be made
  deleteRow = (index) => {
    const postDelete = [...this.state.userNames];
    postDelete.slice(index, 1);
    this.setState({
      userNames: postDelete
    })
  }

  render() {
      return <div>
          <h1>Names</h1>
          <button onClick={this.addChild} style={styles.button}>Add New</button>
          <div>
              {this.state.userNames.map((nameDetails, index) => {
                  if(nameDetails.isEditable) {
                    return <div>
                      <TextArea clicked={(name) => this.saveButton(name, index)}/>
                      </div>;
                  } else {
                    return <div style={styles.namesContainer}>
                    <div onClick={() => this.inputNamesHandler(index)} style={styles.innerContainerComponent}>
                    <div style={styles.firstMargin}><FirstName firstName={nameDetails.firstName}></FirstName></div>
                    <div><LastName lastName={nameDetails.lastName}></LastName></div>
                    </div>
                    <img src={require('../../images/cancel.png')} style={styles.crossBtn} onClick={() => this.deleteRow(index)} />
                    </div>
                  }
              })}   
          </div> 
          </div>     
    }
}

Textarea/Input Component

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

export default class TextArea extends Component {
    constructor(props) {
        super(props);
        this.state = {value:''}

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({value: event.target.value});
    }

    render() {
        return (
            <div>
                <div style={styles.inputContainer}>
                <input type="text" style={styles.textField} value={this.state.value} onChange={this.handleChange}></input>
                <button type="submit" style={styles.saveButton} onClick={() => this.props.clicked(this.state.value)}>Save</button>
                <img src={require('../../images/cancel.png')} style={styles.crossBtn} />
                </div>
            </div>
        )

    }
}

First name and last name are imported by parent component in this way

import React, {Component} from 'react';

export default class FirstName extends Component {
    render() {
        return <div>{this.props.firstName}</div>
    }
}

Last name is also like the code given above.

Dhaval Jardosh
  • 7,151
  • 5
  • 27
  • 69

2 Answers2

2

slice does not modify the original array, but returns a new array with the modified values. You need to assign it to a new variable or use a different method of deleting the value.

let newArray = postDelete.slice(index, index + 1);
this.setState({
  userNames: newArray
})

In fact, since slice does not mutate the array, you could simplify to this:

this.setState((prevState) => ({
  userNames: prevState.userNames.slice(index, index + 1)
)})

However, to accomplish this specific task, you should use a different method like filter

this.setState((prevState) => ({
  userNames: prevState.userNames.filter((v,i) => i != index)
)})

This will iterate through the array and filter out all that don't meet the condition. The first argument is the current value and the second is the index. So we only want to keep the values that do not match our index variable.

Brian Thompson
  • 13,263
  • 4
  • 23
  • 43
  • The issue with this is that it deletes all the items if I click on the 2nd and 3rd element. If I click on the 1st one the other two get deleted. – user9042649 Dec 19 '19 at 22:08
  • 1
    Ah I didn't pay close enough attention to the actual use case here. I'll edit my answer. `slice` takes a start and end index, so we want to start at the given index and cut to the next position (+1) – Brian Thompson Dec 19 '19 at 22:23
  • Now that leaves me only the div which I want to remove. I understand that slice returns the one element we removed but I want to be able to remove that div and show others. What is happening now is it the opposite. – user9042649 Dec 19 '19 at 22:35
  • Makes sense. Then slice is the wrong method. Instead use filter. `.filter((value, i) => i != index)` – Brian Thompson Dec 19 '19 at 23:00
  • Can you tell me where I am going wrong? https://imgur.com/a/qgYMtlR – user9042649 Dec 20 '19 at 05:33
  • The commented out filter looks correct. Is it not working? – Brian Thompson Dec 20 '19 at 14:08
-1

class App extends React.Component {
  state = {
    users: [
      {
        firstName: "Lionel",
        lastName: "Messi"
      },
      {
        firstName: "Cristiano",
        lastName: "Ronaldo"
      },
      {
        firstName: "Neymar",
        lastName: "Jr."
      },
      {
        firstName: "Zlatan",
        lastName: "Ibrahimovic"
      },
      {
        firstName: "Ricardo",
        lastName: "Kaka"
      }
    ]
  };

  updateUsers = (updatedUser, index) => {
    var users = [...this.state.users];
    users[index] = updatedUser;
    this.setState({
      users
    });
  };

  deleteUser = index => {
    var users = [...this.state.users];
    users.splice(index, 1);
    this.setState({
      users
    });
  };

  render() {
    return (
      <div>
        {this.state.users.map((user, index) => {
          return (
            <PlayerBox
              user={user}
              key={Math.random()}
              index={index}
              updateUsers={this.updateUsers}
              deleteUser={this.deleteUser}
            />
          );
        })}
        <h1> {JSON.stringify(this.state.users)} </h1>
      </div>
    );
  }
}

class PlayerBox extends React.Component {
  state = {
    editMode: false,
    firstName: "",
    lastName: ""
  };

  componentDidMount() {
    const { firstName, lastName } = this.props.user;
    this.setState({
      firstName,
      lastName
    });
  }

  updateParent = () => {
    const { index, updateUsers } = this.props;
    updateUsers(
      {
        firstName: this.state.firstName,
        lastName: this.state.lastName
      },
      index
    );
    this.setState({
      editMode: false
    });
  };

  deleteUser = () => {
    const { deleteUser, index } = this.props;
    this.setState({
      editMode: false
    });
    deleteUser(index);
  };

  render() {
    const { firstName, lastName } = this.props.user;
    return this.state.editMode ? (
      <div>
        <div>
          <input
            type="text"
            value={this.state.firstName}
            onChange={e =>
              this.setState({
                firstName: e.target.value
              })
            }
          />
          <input
            type="text"
            value={this.state.lastName}
            onChange={e =>
              this.setState({
                lastName: e.target.value
              })
            }
          />
          <button type="submit" onClick={this.updateParent}>
            Save
          </button>
          <button onClick={this.deleteUser}> Delete </button>
        </div>
      </div>
    ) : (
      <div
        onClick={() =>
          this.setState({
            editMode: true
          })
        }
      >
        {firstName} {lastName}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Dhaval Jardosh
  • 7,151
  • 5
  • 27
  • 69
  • 1
    Your suggestion mutates state and should not be used. You can change to `let users = [...this.state.users];` – Brian Thompson Dec 19 '19 at 22:06
  • please see again if it's mutating or not – Dhaval Jardosh Dec 19 '19 at 22:07
  • Thanks Dhaval, will check this and get back. Hope this works for me – user9042649 Dec 19 '19 at 22:08
  • @DhavalJardosh Did you make an edit? I'm not seeing one – Brian Thompson Dec 19 '19 at 22:09
  • 2
    `var users = this.state.users; users[index] = updatedUser;` You can't do that. You're setting state directly. `users` is a reference to `state.users`, it's not a new array. – JMadelaine Dec 19 '19 at 22:13
  • var users = this.state.users; //I'm copying the value to another variable named `users`, and I do splice on the new variable, which is good. And I'm assigning a new spliced var to the `users` state again. I don't see mutation here, but if you have an explanation I'm quite curious to take a look. – Dhaval Jardosh Dec 19 '19 at 22:14
  • 1
    Thats not true, its still referencing the same array. Check out this [answer](https://stackoverflow.com/a/3638034/9381601). When assigning arrays and objects to variables in javascript, you are assigning it a reference, not the object/array itself. – Brian Thompson Dec 19 '19 at 22:16
  • @DhavalJardosh you're splicing in the `deleteUser` function. `splice` mutates the array directly. And in `updateUsers`, you're manually mutating the array with `users[index] = updatedUser` – Emile Bergeron Dec 19 '19 at 22:20
  • 1
    You're absolutely correct Brian and JMadelaine. Thank you for pointing that out for me. Let me correct the answer. I did learn something. Thanks again. – Dhaval Jardosh Dec 19 '19 at 22:20
  • So @EmileBergeron when I do `var users = [...this.state.users]` and I do a splice, that should be ok, right? – Dhaval Jardosh Dec 19 '19 at 22:24
  • At that point, just use `slice` which returns a copy anyway. But yes, making a shallow copy with `[...this.state.users]` and splicing would _not_ mutate the state. – Emile Bergeron Dec 19 '19 at 22:27
  • 1
    I see great thank you very much for the explanation. – Dhaval Jardosh Dec 19 '19 at 22:28