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

Давайте сделаем инпуты для добавления новых элементов в наш массив.

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

return <div> {result} <br /> <input /> <input /> <input /> <button>save</button> </div>;

В дальнейшем решении задачи мы можем пойти двумя путями: можно для каждого инпута сделать отдельный стейт, либо сделать один общий стейт-объект, содержащий value наших инпутов.

Давайте рассмотрим оба пути по очереди.

Путь первый

Давайте каждому инпуту сделаем свой отдельный стейт:

function App() { const [notes, setNotes] = useState(initNotes); const [value1, setValue1] = useState(''); const [value2, setValue2] = useState(''); const [value3, setValue3] = useState(''); ... }

Добавим обработчики события onChange:

return <div> {result} <br /> <input value={value1} onChange={event => setValue1(event.target.value)} /> <input value={value2} onChange={event => setValue2(event.target.value)} /> <input value={value3} onChange={event => setValue3(event.target.value)} /> <button>save</button> </div>;

Сделаем так, чтобы по нажатию на кнопку вызывалась функция addItem:

return <div> {result} <br /> <input value={value1} onChange={event => setValue1(event.target.value)} /> <input value={value2} onChange={event => setValue2(event.target.value)} /> <input value={value3} onChange={event => setValue3(event.target.value)} /> <button onClick={addItem}>save</button> </div>;

Данная функция должна взять значение каждого инпута из соответствующего стейта, сделать из этих значений объект с новым элементом, а затем добавить этот элемент в массив:

function addItem() { let obj = { prop1: value1, prop2: value2, prop3: value3, }; setNotes([...notes, obj]); }

Кроме того, добавляемый элемент должен иметь уникальный id. Будем генерировать этот id с помощью созданной в одном из предыдущих уроков функции id():

function addItem() { let obj = { id: id(), prop1: value1, prop2: value2, prop3: value3, }; setNotes([...notes, obj]); }

Соберем весь код вместе и получим решение нашей задачи:

function App() { const [notes, setNotes] = useState(initNotes); const [value1, setValue1] = useState(''); const [value2, setValue2] = useState(''); const [value3, setValue3] = useState(''); const result = notes.map(note => { return <p key={note.id}> <span>{note.prop1}</span>, <span>{note.prop2}</span>, <span>{note.prop3}</span> </p>; }); function addItem() { let obj = { id: id(), prop1: value1, prop2: value2, prop3: value3, }; setNotes([...notes, obj]); } return <div> {result} <br /> <input value={value1} onChange={event => setValue1(event.target.value)} /> <input value={value2} onChange={event => setValue2(event.target.value)} /> <input value={value3} onChange={event => setValue3(event.target.value)} /> <button onClick={addItem}>save</button> </div>; }

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

Сделайте так, чтобы текст инпутов очищался после добавления продуктов.

Путь второй

Сделаем так, чтобы значения инпутов были привязаны к одному объекту, вот такому:

const obj = { prop1: '', prop2: '', prop3: '' }

Пусть этот объект также хранит в себе id нового элемента массива:

const obj = { id: 'ай ди нового элемента' prop1: '', prop2: '', prop3: '' }

Сделаем специальную функцию, которая будет возвращать нам описанный объект, сгенерировав в нем случайный id:

function getInitObj() { return { id: id(), prop1: '', prop2: '', prop3: '' } }

Используем эту функцию для получения начального значения стейта:

function App() { const [notes, setNotes] = useState(initNotes); const [obj, setObj] = useState(getInitObj()); // используем функцию ... }

Давайте привяжем части нашего объекта к инпутам:

return <div> {result} <br /> <input value={obj.prop1} /> <input value={obj.prop2} /> <input value={obj.prop3} /> <button>save</button> </div>;

Привяжем в качестве обработчика onChange инпутов общую функцию changeProp:

return <div> {result} <br /> <input value={obj.prop1} onChange={event => changeProp('prop1', event)} /> <input value={obj.prop2} onChange={event => changeProp('prop2', event)} /> <input value={obj.prop3} onChange={event => changeProp('prop3', event)} /> <button>save</button> </div>;

В функции changeProp будем изменять свойство, имя которого передано первым параметром:

function changeProp(prop, event) { setObj({...obj, [prop]: event.target.value}); }

В качестве обработчика клика по кнопке привяжем функцию addItem:

return <div> {result} <br /> <input value={obj.prop1} onChange={event => changeProp('prop1', event)} /> <input value={obj.prop2} onChange={event => changeProp('prop2', event)} /> <input value={obj.prop3} onChange={event => changeProp('prop3', event)} /> <button onClick={addItem}>save</button> </div>;

Внутри функции addItem мы можем сразу же добавлять наш стейт obj в качестве нового элемента массива:

function addItem() { setNotes([...notes, obj]); }

После этого следует вернуть объект в исходное состояние, чтобы произошла очистка инпута и сгенерировался новый id:

function addItem() { setNotes([...notes, obj]); setObj(getInitObj()); }

Таким образом фактически получается, что стейт obj хранит заготовку нового элемента массива с уникальным id. Когда заготовка становится элементом массива, то в obj опять записывается начальная заготовка с новым id. И так далее.

Давайте соберем весь наш код вместе и получим решение нашей задачи:

function getInitObj() { return { id: id(), prop1: '', prop2: '', prop3: '' } } function App() { const [notes, setNotes] = useState(initNotes); const [obj, setObj] = useState(getInitObj()); const result = notes.map(note => { return <p key={note.id}> <span>{note.prop1}</span>, <span>{note.prop2}</span>, <span>{note.prop3}</span> </p>; }); function changeProp(prop, event) { setObj({...obj, [prop]: event.target.value}); } function addItem() { setNotes([...notes, obj]); setObj(getInitObj()); } return <div> {result} <br /> <input value={obj.prop1} onChange={event => changeProp('prop1', event)} /> <input value={obj.prop2} onChange={event => changeProp('prop2', event)} /> <input value={obj.prop3} onChange={event => changeProp('prop3', event)} /> <button onClick={addItem}>save</button> </div>; }

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