reactjs - 系统抛出异常并显示 playerProfile.map 不是函数
问题描述
我想data
在玩家资料字段中显示收到。从字段开始name
,它显示名称数据,但在尝试编辑name
文本字段时,系统抛出以下异常,TypeError: playerProfile.map is not a function
. 我已将fetch
调用包装在箭头函数中。有人可以建议这个错误的根本原因是什么。
注意:目前我只收到了name
字段的值,需要显示其他字段,还需要处理handleSubmit()
Detailed error message from console:
Uncaught TypeError: playerProfile.map is not a function
at Profile (Profile.js:34)
at renderWithHooks (react-dom.development.js:14803)
at updateFunctionComponent (react-dom.development.js:17034)
at beginWork (react-dom.development.js:18610)
at HTMLUnknownElement.callCallback (react-dom.development.js:188)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
at invokeGuardedCallback (react-dom.development.js:292)
at beginWork$1 (react-dom.development.js:23203)
at performUnitOfWork (react-dom.development.js:22157)
我的示例代码
const [playerProfile, setPlayerProfile] = useState([]);
const handleSubmit = (e) => {
e.preventDefault()
}
const onChange = (e) => {
e.persist();
setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });
}
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/profile')
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
return (
<div className="register_player_Twocolumn_layout_two">
<form onSubmit={handleSubmit} className="myForm">
{
playerProfile.map(({ id, image, name, email, position, privilege, password }) =>(
<div>
<div key={id} 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=""></img>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
<input className="inputRequest formContentElement" name="name" type="text" key={name} value={name} onChange={onChange}/>
</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>
);
解决方案
TLDR:检查沙箱
@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"
}
如您所见,除非您获取其keys或entries ,否则您无法映射对象,在这种情况下,该方法仍然对对象中的第二个元素无效。
- 另一个问题是您正在尝试更新对象,并将其直接附加到数组/对象。如果更新成功,这将导致名称的重复数据保存。您需要找到状态中的旧对象并更新它,然后完全替换它。如果您的数据被规范化,那会很好,就像最初通过键保存一样。像这样的东西...
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 的输入标签) not [e.target.name]
,同时使用playerProfileIds
来映射 jsx 中的项目。
- 但是,如果您无法控制 api 数据格式,则可以改为确保更新处理程序以
id
从 onChange 接收(假设 id 是唯一的),使用该 id 在数组中查找配置文件。- 在查找时,您可以保留元素数组索引,并使用它直接定位和更新数组。(处理程序中注释掉的方法)
- 或者您可以映射整个数组并更新更改的配置文件,然后使用该数据最终更新状态。
以下是完整的方法。
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
道具,这样反应可以知道哪个组件完全更改以避免重新渲染。在您的方法中,您将密钥分配给树深处的输入。将它向上移动到最外面的 div 包装器。
还要确保您用作密钥的任何项目都是唯一的,否则如果密钥更改,这些项目将继续失去对更新的关注。例如,在您的代码中,名称正在更改,但您将其用作输入。这导致了一个新的关键,这意味着您正在处理一个新元素,最终失去了对该输入的关注。
推荐阅读
- mysql - 将mysql数据库中的一列拆分为两列
- php - 如何在 PHP 中使用谷歌广告词 API 创建产品组
- android - Retrofit2 不能调用 onResponse 和 onFailure?
- javascript - QZ托盘生印刷
- react-native - React Native:在 react-native-maps 中设置 UserLocation 样式
- html - 引导 4 个选项卡
- mongodb - 基于本地密钥管理验证 MongoDB 加密
- ionic-framework - 离子地图框错误
- json - 如何使用 spring data jpa 存储库(jackson)反序列化已发布的包含嵌套数据的 json?
- sql - 在 sql 中显示 locationcode 而不是 Location