【问题标题】:System throws exception and display playerProfile.map is not a function系统抛出异常并显示 playerProfile.map 不是函数
【发布时间】:2020-05-26 01:24:10
【问题描述】:

我想在玩家资料字段中显示收到的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>
    );

【问题讨论】:

  • 在返回语句之前登录playerProfile会发生什么?您在日志中收到什么价值/价值
  • setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value }); playerProfile 是一个对象而不是一个数组。 .map 是数组而非对象的成员。你想在那里做什么?
  • @soccerway,playerProfile的结构是什么样的?如果像 @RossAllen 暗示你的数据是一个对象,你最好使用 Object.entries() 并映射它
  • 网络标签中的结构显示,数据如下{"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"}]}
  • @iamcastelli 我已经添加了结构供参考..

标签: reactjs express axios react-hooks


【解决方案1】:

TLDR:检查沙盒

@soccerway,根据我们根据您方法中的拼写错误指出的 cmets,这里有一些尝试修复它们的代码。 Live Codesandbox的链接

一些上下文

  1. 当您定义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"
}

如您所见,您无法映射对象,除非您获得其 keysentries,在这种情况下,该方法仍然会因对象中的第二个元素而无效。

  1. 另一个问题是您试图更新一个对象,并将其直接附加到数组/对象。如果更新成功,这将导致名称的重复数据保存。您需要找到状态中的旧对象并对其进行更新,然后完全替换它。如果您的数据被规范化,那会很好,就像最初通过键保存一样。像这样...

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。


  1. 但是,如果您无法控制 api 数据格式,则可以改为确保更新处理程序以从 onChange 接收 id(假设 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 道具,这样 react 可以知道哪个组件完全更改以避免重新渲染。在您的方法中,您将密钥分配给树深处的输入。将其移至最外层的 div 包装器。

还要确保您用作密钥的任何项目都是唯一的,否则如果密钥更改,这些项目将继续失去关注更新。例如,在您的代码中,名称正在更改,但您将其用作输入。这会产生一个新的关键,这意味着您正在处理一个新元素,最终失去了对该输入的关注。

【讨论】:

  • ...非常感谢您的帮助。谢谢你。现在错误消失了,但如果我添加 onChange={e =&gt; handleChange(e, id)} 我无法编辑显示大卫的名称字段
  • 等一下,我正在为代码添加一些解释。但这可能是因为您可能忘记了您仍然拥有 key={name} 名称输入集。这使得反应假设它只是另一个元素。你检查过沙箱吗?它是否表现出相同的行为?喜欢该字段不会更新?
  • 您是说沙箱不适合您,还是您对代码所做的更改不起作用。抱歉,我问这个问题,我想弄清楚您是否检查了错误的框或在更新代码时遇到了棘手的问题
  • 很棒的帮助,接受并赞成答案,清楚地阐明了每个部分......
  • 很高兴你终于弄明白了。快乐编码。 PS,如果你规范化该数据。它会让你不那么头疼。
【解决方案2】:

这可能是您的问题之一

<label>
  <input className="inputRequest formContentElement" 
   name="name" type="text" key={name} value={name} 
   onChange ={onChange}/>
</label>

输入标签的name属性的值“name”不在playerProfile的数组中。 我认为应该是:

<label>
      <input className="inputRequest formContentElement" 
       name={name} type="text" key={name} value={name} 
       onChange ={onChange}/>
</label>

这意味着你的问题应该在这里

setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });

playerProfile 是一个数组,但上面的行将它设置为一个对象,它会抛出错误

这可能有效:

setPlayerProfile([ ...playerProfile, [e.target.name]: e.target.value ]);

【讨论】:

  • 我已将名称作为名称字段的文本字段。这没有帮助
  • 这意味着你的问题应该在这里 setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value }); playerProfile 是一个数组,但上面的行将它设置为一个抛出错误的对象
猜你喜欢
  • 2018-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多