Система выдает исключение, и отображение playerProfile.map не является функцией - PullRequest
1 голос
/ 26 мая 2020

Я хотел бы отображать data полученное в полях профиля игрока. Начиная с поля name, он отображает данные имени, но при попытке отредактировать текстовое поле name система выдает следующее исключение, TypeError: playerProfile.map is not a function. Я заключил вызов fetch в функцию стрелки. Может кто-нибудь посоветовать, в чем причина этой ошибки root.

Примечание: на данный момент я получил значение только для поля 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>
    );

Ответы [ 2 ]

1 голос
/ 26 мая 2020

TL; DR: проверьте песочницу

@ 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"
}

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

Другая проблема заключается в том, что вы пытаетесь обновить объект и напрямую добавляете его к массиву / объекту. Если обновление прошло успешно, это приведет к дублированию данных, сохраненных для имени. Вам нужно найти старый объект в состоянии и обновить его, а затем полностью заменить. Было бы хорошо, если бы ваши данные были нормализованы, например, изначально сохранены с помощью ключей. Что-то вроде этого ...

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.

Также убедитесь, что любой элемент, который вы используете в качестве ключа, уникален, иначе элементы будут терять фокус при обновлении, если ключ изменится. Например, в вашем коде имя изменяется, но вы используете его как вход. Это приводит к новому ключу, означающему, что вы работаете над новым элементом, и в конечном итоге теряете фокус на этом вводе.

0 голосов
/ 26 мая 2020

Это может быть одна из ваших проблем

<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 ]);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...