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

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

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>;
}