首页 > 解决方案 > 系统抛出异常并显示 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>
    );

标签: reactjsexpressaxiosreact-hooks

解决方案


TLDR:检查沙箱

@soccerway,根据我们根据您的方法中的拼写错误指出的评论,这里有一些尝试修复它们的代码。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 的输入标签) not [e.target.name],同时使用playerProfileIds来映射 jsx 中的项目。


  1. 但是,如果您无法控制 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 包装器。

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


推荐阅读