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