TL; DR: проверьте песочницу
@ soccerway, на основании наших комментариев, отмеченных в соответствии с вашими опечатками, вот код, который пытается их исправить. Ссылка на Live Codesandbox
НЕКОТОРЫЙ контекст
- Когда вы определяете состояние своего
playerProfile
компонента, вы инициализируете его как массив, успешно обновляете его из сервер как массив, но испортил его во входном обработчике onChange
. Допустим, вы набираете s в поле ввода имени. С этим ...
setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });
... вы преобразовываете playerProfile
из этого массива.
// Fetched playerProfile from the api.
playerProfile = [
{
name: "David",
email: "david@testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
// Extra profile put to have a solid example
{
name: "Goriath",
email: "goriath@testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
]
// To This Object
playerProfile = {
0: {
name: "David",
email: "david@testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
1: {
name: "Goriath",
email: "goriath@testmail.com",
phonenumber: null,
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
name: Davids"
}
Как видите, вы не можете сопоставить объект, если только не получите его ключи или записи , и в этом случае подход все равно будет недействительным для второго элемента в объекте.
Другая проблема заключается в том, что вы пытаетесь обновить объект и напрямую добавляете его к массиву / объекту. Если обновление прошло успешно, это приведет к дублированию данных, сохраненных для имени. Вам нужно найти старый объект в состоянии и обновить его, а затем полностью заменить. Было бы хорошо, если бы ваши данные были нормализованы, например, изначально сохранены с помощью ключей. Что-то вроде этого ...
data= {
playerProfilesById = {
5: { // Player ID is the key
name: "David",
email: "david@testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
6: {
name: "Goriath",
email: "goriath@testmail.com",
phonenumber: null,
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
},
playerProfileIds=[5,6]
}
Таким образом, его легко обновить playerProfilesById
с вашим подходом, с [e.target.id]
(при условии, что вы передаете входной тег, это id), а не [e.target.name]
, при использовании playerProfileIds
для сопоставления элементов в jsx.
Однако, если у вас нет контроля над форматом данных api, вы можете вместо этого убедиться, что обновили свой обработчик, чтобы получить
id
(при условии, что идентификатор уникален) из onChange, используйте этот идентификатор для поиска профиля в массиве.
- При поиске вы можете сохранить индекс массива элементов и использовать его для прямого нацеливания и обновления массива. ( Закомментированный подход в обработчике )
- Или вы можете просто сопоставить весь массив и обновить измененный профиль, а затем использовать эти данные для последующего обновления состояния.
Ниже представлен полный подход.
import React, { useState, useEffect } from "react";
// import axios from "axios";
/* Assuming your api returns data in the follwoing format... */
const fakeAPICall = () => {
// CALL TO AXIO MUTED
// const res = await axios.get("http://localhost:8000/service/profile");
// NOTE: Please normalize this data so it's easy to update
// READ ABOUT: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
const data = {
playerProfile: [
{
name: "David",
email: "david@testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
{
name: "Goriath",
email: "goriath@testmail.com",
phonenumber: "1234345234",
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDFIELDER",
updatedAt: "2020-05-26T11:02:16.000Z"
}
]
};
return { data };
};
const PlayerProfile = () => {
// Note that your player profile is defined as an array in state.
// Remember to always keep it that way when updating it.
const [playerProfile, setPlayerProfile] = useState([]);
const handleSubmit = e => {
e.preventDefault();
};
// Pass the id to the handler so you will know which item id changing.
const handleChange = (e, id) => {
e.persist();
let itemIndex;
const targetPlayer = playerProfile.find((player, index) => {
console.log({ player, id, index });
itemIndex = index; // Track the index so you can use it to update later.
return player.id === id;
});
console.log({ targetPlayer, id, e });
const editedTarget = {
...targetPlayer,
[e.target.name]: e.target.value
};
const tempPlayers = Array.from(playerProfile);
tempPlayers[itemIndex] = editedTarget;
/*
// Alternatively:: you can just map over the array if you dont want to track the index
const tempPlayers = playerProfile.map((profile, index) => {
return profile.id === id ? editedTarget : profile;
});
*/
setPlayerProfile(tempPlayers);
};
useEffect(() => {
const fetchData = async () => {
try {
// const res = await axios.get("http://localhost:3000/api/products");
const res = await fakeAPICall();
console.log({ response: res });
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
console.log({ "⚽: playerProfile": playerProfile });
return (
<div className="register_player_Twocolumn_layout_two">
<h1>CAPTURE PLAYER PROFILE</h1>
<p>Form to capture player Profile</p>
<hr />
<form onSubmit={handleSubmit} className="myForm">
{playerProfile.map(
({ id, image, name, email, position, privilege, password }) => (
<div key={id}>
{/*2. Also put the key on the outer div in the map above */}
<div className="formInstructionsDiv formElement">
<h2 className="formTitle">Player Profile</h2>
<div className="register_profile_image">
<input id="profilePic" name="photo" type="file" />
</div>
<div className="previewProfilePic">
<img
alt=""
error=""
name="previewImage"
className="playerProfilePic_home_tile"
src=""
/>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
NAME
<input
className="inputRequest formContentElement"
name="name"
type="text"
// key={name} // Remove this key or renmae it to id. Since name changes on rerender, it confuses react that the key is different and forces the element to toose focus
value={name}
onChange={e => handleChange(e, id)} // Pass the ID form here.
/>
</label>
<label>
<input
className="inputRequest formContentElement"
name="email"
type="text"
/>
</label>
<label>
<div className="select">
<select name="privilege" id="select">
<option value="player">PLAYER</option>
<option value="admin">ADMIN</option>
</select>
</div>
</label>
<label>
<input
className="inputRequest formContentElement"
name="password"
type="password"
/>
</label>
</div>
<div className="submitButtonDiv formElement">
<button type="submit" className="submitButton">
Save
</button>
</div>
</div>
)
)}
</form>
</div>
);
};
export default function App() {
return (
<div className="App">
<PlayerProfile />
</div>
);
}
PS : при сопоставлении элементов каждая прямая оболочка ожидает уникальный key
prop, таким образом React может знать, какой именно компонент был изменен, чтобы избежать повторного рендеринга. В своем подходе вы назначаете ключ входу глубоко в дереве. Переместите его во внешнюю оболочку div.
Также убедитесь, что любой элемент, который вы используете в качестве ключа, уникален, иначе элементы будут терять фокус при обновлении, если ключ изменится. Например, в вашем коде имя изменяется, но вы используете его как вход. Это приводит к новому ключу, означающему, что вы работаете над новым элементом, и в конечном итоге теряете фокус на этом вводе.