#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 отображался должным образом).