Универсальная форма для изменения массива в React


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

Если инпут в режиме добавления, то по потери фокуса в массив добавится новый элемент. А если инпут в режиме редактирования, то абзац будет редактироваться одновременно с инпутом, а по потери фокуса инпут перейдет в режим добавления.

Для решения описанной задачи есть несколько подходов.

Подход первый
Сделаем два инпута и с помощью условного рендеринга будем показывать или инпут для добавления, или инпут для редактирования.

Вот реализация описанного:

function App() {
const [notes, setNotes] = useState([1, 2, 3, 4, 5]);
const [editNum, setEditNum] = useState(null);
const [value, setValue] = useState('');

const result = notes.map((note, index) => {
return <p key={index} onClick={() => setEditNum(index)}>
{note}
</p>;
});

function changeItem(event) {
setNotes([...notes.slice(0, editNum), event.target.value,...notes.slice(editNum + 1)]);
}
function stopEdit(event) {
setEditNum(null);
}

function changeValue(event) {
setValue(event.target.value)
}
function addItem(event) {
setNotes([...notes, value]);
}

let input;
if (editNum) {
input = <input
value={notes[editNum]}
onChange={changeItem}
onBlur={stopEdit}
/>
} else {
input = <input
value={value}
onChange={changeValue}
onBlur={addItem}
/>
}

return <div>
{result}
{input}
</div>;
}
Подход второй
Совместим обе операции в одном инпуте:

function App() {
const [notes, setNotes] = useState([1, 2, 3, 4, 5]);
const [editNum, setEditNum] = useState(null);
const [value, setValue] = useState('');

const result = notes.map((note, index) => {
return <p key={index} onClick={() => startEdit(index)}>
{note}
</p>;
});

function startEdit(index) {
setEditNum(index);
setValue(notes[index]);
}
function changeHandler(event) {
setValue(event.target.value);

if (editNum) {
setNotes([...notes.slice(0, editNum), event.target.value,...notes.slice(editNum + 1)]);
}
}
function blurHandler(event) {
if (!editNum) {
setNotes([...notes, value]);
} else {
setEditNum(null);
}

setValue('');
}

return <div>
{result}
<input value={value} onChange={changeHandler} onBlur={blurHandler} />
</div>;
}
Подход третий
Сделаем так, чтобы при добавлении нового элемента он сразу появлялся в виде нового абзаца. И при наборе текста в инпуте в этом абзаце сразу набирался текст нового элемента.

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

Реализуем:

function App() {
const [notes, setNotes] = useState([1, 2, 3, 4, 5]);
const [editNum, setEditNum] = useState(null);

const result = notes.map((note, index) => {
return <p key={index} onClick={() => startEdit(index)}>{note}</p>;
});

function startEdit(index) {
setEditNum(index);
}
function editItem(event) {
setNotes([...notes.slice(0, editNum), event.target.value,...notes.slice(editNum + 1)]);
}
function createItem() {
if (!editNum) {
const res = [...notes, ''];
setNotes(res);
setEditNum(res.length - 1);
}
}
function stopEdit() {
setEditNum(null);
}

return <div>
{result}

<input
value={editNum ? notes[editNum] : ''}
onChange={editItem}
onFocus={createItem}
onBlur={stopEdit}
/>
</div>;
}