Как удалить индекс из массива в react при определенном нажатии

#javascript #arrays #reactjs

#javascript #массивы #reactjs

Вопрос:

Я создаю конструктор тестов / опросов, своего рода интерфейс CMS, который позволит пользователям добавлять столько вопросов, сколько они хотят, нажимая на нужный тип вопроса.

Для начала мое состояние настраивается в компоненте приложения следующим образом:

 state = {
  components: API.components,
  comps: []
}
  

На экране администратора есть выбранное количество кнопок, которые активируют вопрос при нажатии. Вопрос исходит из API.components .
Например, у нас есть:
— Приветственное сообщение
— Благодарственное сообщение
— Да или нет

 /* 1. Generate a list of buttons with onClick props.addQuestion passing a fixed id via 'i' parameter */
const QuestionTypes = props => {  
  return (
    <div data-name='typequestion'>
      {props.details.map((el, i) => <div key={i}><button className="sm" onClick={() => props.addQuestion(i)}>{el.label}</button></div>)}
    </div>
  )
}
  

Смотрите Скриншот вопросов меню: https://www.awesomescreenshot.com/image/3982311/8964261115690780c3f67d390ce08665

При нажатии каждой из этих кнопок запускается метод addQuestion, который передает фиксированный идентификатор (ключ) функции selectComponent для добавления выбранного компонента в массив comps[]:

 /* onClick, a method 'addQuestion' is called */
/* This method will setState and call a function selectComponent passing a key (id) in order to select the correct component */
addQuestion = key => {
  this.setState({
    comps: [...this.state.comps, this.selectComponent(key)]
  });
}
  

Функция selectComponent имеет переключатель для передачи правильного компонента:

 selectComponent = (key) => {
    switch(key) {
      case 0:
        return <WelcomeMessage details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      case 1:
        return <ThankYouMessage details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      case 2:
        return <YesNo details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      default:
        return 'Please select a component from the left side menu'
    }
  }
  

Это добавит элемент в массив comps[]:

 [
  0: {element here ..}
  1: {element here ..} etc.
]
  

Вот пример кода для компонента:

 const ThankYouMessage = props => (

  <section className="ui-component-view" data-name="thankyou">
    <img src={ThankYouImage} alt="x" />
    <h3>Thanks for completing our form!</h3>

    <div>
      <DeleteButton deleteQuestion={props.deleteQuestion} />
    </div>
  </section>

);
  

Смотрите Скриншот выбранного компонента приветственного сообщения: https://www.awesomescreenshot.com/image/3982315/f59e1bf79a31194aa3ee3ad2467658a0

PROBLEM:

Как вы можете видеть, у каждого компонента будет кнопка удаления. Хотя каждый компонент добавляется в массив без проблем, я не могу найти способ удалить ТОЛЬКО выбранный компонент, когда я нажимаю кнопку удаления.

Я пытался использовать .filter(), splice() но у меня нет правильного индекса для вновь созданного или обновленного списка массивов. Я хочу использовать для этого способ React, а не jQuery или Javascript.

Пример кнопки удаления. Пожалуйста, обратите внимание, что props.index передает исходный идентификатор нажатой кнопки (ключ), который не будет соответствовать новому индексу массива comps[]:

 const DeleteButton = props => (

  <span className="deleteButton" onClick={() => props.deleteQuestion(props.index)}>amp;times;<small>Delete</small></span>

);

export default DeleteButton;
  

Здесь метод удаления:

 deleteQuestion = e => {
    const comps = [...this.state.comps]

    // 2. here I need to add something that will DELETE ONLY the clicked button index for that component

    // 3. Update state
    this.setState({ comps });
}
  

Пожалуйста, ознакомьтесь с полным кодом компонента приложения:
класс App расширяет React.Компонент {

   state = {
    components: API.components,
    comps: [],
    multichoice: {}
  }

  selectComponent = (key) => {
    switch(key) {
      case 0:
        return <WelcomeMessage details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      case 1:
        return <ThankYouMessage details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      case 2:
        return <YesNo details={this.state.components[key]} deleteQuestion={this.deleteQuestion} comps={this.state.comps} index={fuck} />
        break;
      default:
        return 'Please select a component from the left side menu'
    }
  }

  addQuestion = key => {

    this.setState({
      comps: [...this.state.comps, this.selectComponent(key)]
    });
  }

  deleteQuestion = e => {
    const comps = [...this.state.comps]

    // 2. here I need to add something that will DELETE ONLY the component related to the delete button

    // 3. Update state
    this.setState({ comps });
  }

  render() {
    return (
      <Container>
        <Row>
          <Col>
            <h1>Survey Builder </h1>
          </Col>
        </Row>
        <Row>
          <Col lg={3} className="border-right">
          <QuestionTypes addQuestion={this.addQuestion} details={this.state.components} />
          </Col>
          <Col lg={9}>
            <Row>
              <Col lg={12}>
                <QuestionEdit comps={this.state.comps} details={this.state.components} />
              </Col>
            </Row>
          </Col>
        </Row>
      </Container>
    )
  }
}

export default App;
  

Ответ №1:

Вы не должны хранить компоненты внутри состояния (причина, которая нарушает жизненный цикл компонентов, и их трудно сравнивать). Вместо этого просто сохраните ключи:

  addQuestion = key => {
  this.setState({
   comps: [...this.state.comps, key]
  });
 }
  

Затем внутри render() сопоставьте ключи с компонентами:

  {this.state.comps.map((key, index) => <SomeComponent    remove={() => this.removeQuestion(index)} />)}
  

Теперь removeQuestion можно просто:

  removeQuestion(index) {
   this.setState(({ comps }) => ({ comps: comps.filter((_, i) => i !== index) }));
 }
  

Комментарии:

1. Привет, JonasWilms, ‘e’ передает идентификатор, который является фиксированным номером идентификатора кнопки. Они не совпадают с массивом индексов. Например, в то время как e передает значение 1 (потому что я нажал кнопку WelcomeMessage), компонент может находиться в индексе 3 массива comps[], потому что я добавил другие вопросы. Итак, используя e в его нынешнем виде, ничего не сработает, даже . Фильтр. Я перепробовал их все. Мне нужно иметь возможность передавать в e значение индекса, относящееся к кнопке удаления, которую я нажал. Имеет ли это смысл?

2. @joe о, я полностью пропустил это: У вас вообще не должно быть компонентов в состоянии…

3. не могли бы вы уточнить? Что вы имеете в виду, говоря «У вас вообще не должно быть компонентов в состоянии»? Извините, я совсем новичок в этом языке. Не могли бы вы привести несколько примеров? Спасибо

4. Извини, приятель, не видел изменений выше. Я посмотрю. Спасибо

5. Привет, Джонас, спасибо за код. Теперь каждая кнопка всегда показывает один и тот же компонент, потому что я добавил {<WelcomeMessage remove={() => this.removeQuestion(index)} />)} . Что было бы наилучшей практикой для обработки всех других компонентов? Оператор If в карте функций? (аналогично переключателю). Мой способ JS заключался бы в использовании конкатенации для создания имени компонента. При нажатии кнопка будет передавать константу с именем ‘WelcomeMessage’, и я бы сделал что-то вроде ‘<‘ constName ‘>’, но я знаю, что это не работает. Спасибо за вашу помощь до сих пор. Мы к чему-то приближаемся

Ответ №2:

ОТРЕДАКТИРОВАНО: никакие компоненты не должны храниться в состоянии, только объекты, представляющие вопросы.

comps Состояние должно быть неизменяемым, это означает, что каждый раз, когда вопрос добавляется или удаляется, вы должны создавать новый массив из старого, находящегося в текущем состоянии.

Поскольку вы не используете какие-либо продвинутые менеджеры состояний (например, Redux и т. Д.), И вам не следует этого делать на данный момент, я бы предложил иметь атрибут data для каждого вопроса с идентификатором вопроса. После нажатия вы можете получить идентификатор вопроса из целевого объекта, который переносит событие click, и использовать его, чтобы выяснить, где находится элемент вопроса в comps состоянии. Как только он у вас есть, создайте новое comps состояние, создав новый массив, в котором нет того вопроса, который вы только что удалили.

Я также хотел бы рекомендовать не использовать здесь переключатель / регистр, поскольку он противоречит принципу открытия / закрытия. Я думаю, вы найдете словарный подход, при котором вы сопоставляете тип вопроса соответствующему компоненту, гораздо более масштабируемому и удобному в обслуживании.

Надеюсь, это поможет 🙂

Комментарии:

1. Привет @Matti-Bar-Zeev, Re: switch / case, изначально я хотел создать универсальный компонент с динамическим введением имени компонента, как я бы сделал в JS: ‘<‘ nameCompenentCalled ‘ />’. Не удалось найти способ сделать это в React. Есть ли какой-нибудь способ случайно? Я не знаком со словарем, но я проведу расследование. Re: ИДЕНТИФИКАТОР в data- *, если у меня есть 10 приветственных сообщений, все они будут иметь одинаковый идентификатор, полученный от нажатой кнопки. Я хотел бы удалить, используя индекс comps[], связанный с кнопкой удаления, которую я только что нажал.

2. @Joe каждый компонент должен иметь отдельный идентификатор, даже если он отображает одно и то же (используйте сопоставление по массиву, чтобы создать список элементов, каждый должен иметь свой собственный ключ, который является производным от идентификатора элемента, чтобы React отображался должным образом).