Форма для редактирования массива объектов в React
Пусть у нас опять есть массив объектов initNotes из предыдущего урока, элементы которого выводятся в виде абзацев:
function App() {
const [notes, setNotes] = useState(initNotes);
const result = notes.map(note => {
return <p key={note.id}>
<span>{note.prop1}</span>,
<span>{note.prop2}</span>,
<span>{note.prop3}</span>
</p>;
});
return <div>
{result}
</div>;
}
Давайте под нашими абзацами сделаем инпуты для редактирования данных наших абзацев. Пусть в конце каждого абзаца будет кнопка для редактирования.
По нажатию на кнопку данные абзаца должны попасть в инпуты. При редактировании инпутов реактивно будет изменяться текст абзаце.
Давайте реализуем описанное.
Шаг 1
Для начала давайте сделаем стейт editId, хранящий в себе id элемента, который редактируется в настоящий момент. Если ничего не редактируется (например, по умолчанию), пусть этот стейт имеет значение null:
function App() {
const [notes, setNotes] = useState(initNotes);
const [editId, setEditId] = useState(null);
...
}
Добавим теперь в конец абзаца кнопку, которая будет записывать id абзаца в editId:
const result = notes.map(note => {
return <p key={note.id}>
<span>{note.prop1}</span>,
<span>{note.prop2}</span>,
<span>{note.prop3}</span>
<button onClick={() => setEditId(note.id)}>edit</button>
</p>;
});
Шаг 2
Давайте теперь сделаем так, чтобы в инпутах выводился текст редактируемого абзаца.
Для этого нам нужно из массива получить редактируемый объект по его id и в каждый инпут записать соответствующее свойство этого объекта.
Пусть это значение извлекает специальная функция getValue:
<input value={getValue('prop1')} />
<input value={getValue('prop2')} />
<input value={getValue('prop3')} />
Давайте напишем реализацию этой функции:
function getValue(prop) {
return notes.reduce((res, note) => {
if (note.id === editId) {
return note[prop];
} else {
return res;
},
}, '');
}
Представим его в более коротком варианте:
function getValue(prop) {
return notes.reduce((res, note) => note.id === editId ? note[prop] : res, '');
}
Шаг 3
Давайте теперь сделаем так, чтобы при изменении любого инпута изменялось значение соответствующего свойства соответствующего элемента массива.
Для этого каждому инпуту в качестве обработчика события onChange привяжем функцию:
<input
value={getValue('prop1')}
onChange={event => changeItem('prop1', event)}
/>
<input
value={getValue('prop2')}
onChange={event => changeItem('prop2', event)}
/>
<input
value={getValue('prop3')}
onChange={event => changeItem('prop3', event)}
/>
Давайте напишем реализацию функции changeItem:
function changeItem(prop, event) {
setNotes(notes.map(note => {
if (note.id === editId) {
return {...note, [prop]: event.target.value};
} else {
return note;
}
}));
}
Упростим код:
function changeItem(prop, event) {
setNotes(notes.map(note =>
note.id === editId ? {...note, [prop]: event.target.value} : note
));
}
Шаг 4
После инпутов сделаем кнопку, нажатие на которую будет завершать редактирование. В нашем случае это означает просто очистку инпутов.
Для этого установим стейт editId в null:
<button onClick={() => setEditId(null)}>save</button>
Шаг 5
Соберем все вместе и получим решение нашей задачи:
function App() {
const [notes, setNotes] = useState(initNotes);
const [editId, setEditId] = useState(null);
const result = notes.map(note => {
return <p key={note.id}>
<span>{note.prop1}</span>,
<span>{note.prop2}</span>,
<span>{note.prop3}</span>
<button onClick={() => setEditId(note.id)}>edit</button>
</p>;
});
function getValue(prop) {
return notes.reduce((res, note) => note.id === editId ? note[prop] : res, '');
}
function changeItem(prop, event) {
setNotes(notes.map(note =>
note.id === editId ? {...note, [prop]: event.target.value} : note
));
}
return <div>
{result}
<br />
<input
value={getValue('prop1')}
onChange={event => changeItem('prop1', event)}
/>
<input
value={getValue('prop2')}
onChange={event => changeItem('prop2', event)}
/>
<input
value={getValue('prop3')}
onChange={event => changeItem('prop3', event)}
/>
<button onClick={() => setEditId(null)}>save</button>
</div>;
}