Форма для редактирования массива объектов в 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>; }

Практические задачи

Возьмите таблицу с продуктами initProds. Сделайте под таблицей форму для редактирования продукта. Добавьте в таблицу еще одну колонку, в которой будут кнопки для редактирования продуктов.