You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React集成Symfony 6 API搜索功能遇map读取未定义错误

问题:Symfony 6 API搜索功能集成到React表格时出现TypeError

我正尝试将Symfony 6 API的搜索功能集成到React表格中。点击搜索按钮后,服务器返回200状态响应,但随即抛出错误:Uncaught TypeError: Cannot read properties of undefined (reading 'map')。请问这是什么原因?

客户端React代码

import React, { useEffect, useState } from "react";
import { Modal, Button, TextField } from '@material-ui/core';
import DeleteIcon from '@material-ui/icons/Delete';
import BorderColorIcon from '@material-ui/icons/BorderColor';
import { Edit } from "../../components/Edit";
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import brand from 'boss-api/dummy/brand';
import { withStyles } from '@material-ui/core/styles';
import { SourceReader, PapperBlock } from 'boss-components';

const styles = ({
  root: {
    flexGrow: 1,
  },
  button: {
    margin: '1rem',
  },
  modal: {
    margin: 'auto',
    maxWidth: 600,
    width: '90%',
    height: '100%'
  },
  paper: {
    backgroundColor: 'white',
    boxShadow: '5px 5px 20px rgba(0, 0, 0, 0.3)',
    padding: '2rem',
    borderRadius: '5px',
  },
  textField: {
    marginBottom: '1rem',
  },
});

function Gebruiker2(props) {
  const [users, setUsers] = useState([]);
  const [openModal, setOpenModal] = useState(false);
  const [usernameSearch, setUsernameSearch] = useState('');
  const { classes } = props;
  const [newUser, setNewUser] = useState({
    firstName: '',
    email: '',
    phoneNumber: '',
    street: '',
    houseNumber: '',
    postalCode: '',
    city: '',
    province: '',
    roleName: '',
    username: '',
    password: ''
    });

    const handleAddUser = () => {
      setOpenModal(true);
    };
  
    const handleCloseModal = () => {
      setOpenModal(false);
    };

    const handleChange = (event) => {
      const { name, value } = event.target;
      setNewUser(prevUser => ({
        ...prevUser,
        [name]: value,
      }));
    };
    
  useEffect(() => {
    fetch('https://127.0.0.1:8000/users')
      .then(response => {
        console.log(response);
        return response.json();
      })
      .then(data => setUsers(data.personInfo))
      .catch(error => console.log(exrror));
  }, []);

  const onAdd = () => {
    fetch('https://127.0.0.1:8000/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newUser)
    })
      .then(response => {
        console.log(response);
        return response.json();
      })
      .then(data => {
        console.log(data);
        handleCloseModal(); // Close the modal after the user has been added
      })
      .catch(error => console.log(error));
  };

  const onEdit = async (id, name, email) => {
    await fetch(`https://127.0.0.1:8000/auth/edit/${id}`, {
      method: "PUT",
      body: JSON.stringify({
        name: name,
        email: email
      }),
      headers: {
        "Content-type": "application/json; charset=UTF-8"
      }
    })
      .then((response) => {
        if (response.status !== 200) {
          return;
        } else {
          return response.json();
        }
      })
      .then((data) => {
        const updatedUsers = users?.map((user) => {
          if (user.id === id) {
            user.name = name;
            user.email = email;
          }
        
          return user;
        });
        

        setUsers((users) => updatedUsers);
      })
      .catch((error) => console.log(error));
  };

  const onDelete = (id) => {
    fetch(`https://127.0.0.1:8000/auth/delete/${id}`, {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
    })
      .then(response => {
        console.log(response);
        return response.json();
      })
      .then(data => {
        console.log(data);
        const updatedUsers = users.filter(user => user.id !== id);
        setUsers(updatedUsers); // Remove the deleted user from the state
      })
      .catch(error => console.log(error));
  };

  const handleSearch = () => {
    fetch(`https://127.0.0.1:8000/users/search`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: usernameSearch }),
    })
      .then(response => {
        console.log(response);
        return response.json();
      })
      .then(data => {
        if (Array.isArray(data)) {
          setUsers(data.personInfo)
        } else {
          console.log("Invalid response format");
        }
      })
      .catch(error => console.log(error));
  };
  
  const handleSearchInputChange = (event) => {
    setUsernameSearch(event.target.value);
  };

    return (
      <>
        <div className="App">
        <PapperBlock whiteBg icon="ion-ios-create-outline" title="In-Row Editable Cell" desc="The Editable Table Cell supports editing features including creating, updating and deleting rows. The editing state contains information about rows currently being edited, changes applied to a particular row, and rows that have been deleted and created.">
          <Button className={classes.button} variant="contained" color="primary" onClick={handleAddUser}>Add New User</Button>
          <TextField
          label="Search by username"
          variant="outlined"
          value={usernameSearch}
          onChange={handleSearchInputChange}
          className={classes.textField}
        />

        <Button onClick={handleSearch}>Search</Button>
          <table>
            <thead>
              <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Actions</th>
              </tr>
            </thead>
            <tbody>
              {users.map((user) => (
                <tr key={user.id}>
                  <td>{user.id}</td>
                  <td>{user.name}</td>
                  <td>{user.email}</td>
                  <td>
                    <Edit
                      id={user.id}
                      name={user.name}
                      email={user.email}
                      onEdit={onEdit}
                      onDelete={onDelete}
                    />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          </PapperBlock>
        </div>

      <Modal
      open={openModal}
      onClose={handleCloseModal}
      className={classes.modal}
      >
      <div className={classes.paper}>
        <h2>Add New User</h2>
        <TextField
        className={classes.textField}
        label="First Name"
        name="first_name"
        value={newUser.first_name}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Email"
        name="email"
        value={newUser.email}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Phone Number"
        name="phone_number"
        value={newUser.phone_number}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Street"
        name="street"
        value={newUser.street}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="House Number"
        name="house_number"
        value={newUser.house_number}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Postal Code"
        name="postal_code"
        value={newUser.postal_code}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="City"
        name="city"
        value={newUser.city}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Province"
        name="province"
        value={newUser.province}
        onChange={handleChange}
        fullWidth
      />
      <TextField
        className={classes.textField}
        label="Role Name"
        name="role_name"
        value={newUser.role_name}
        onChange={handleChange}
        fullWidth
      />

      <TextField
      className={classes.textField}
      label="Username"
      name="username"
      value={newUser.username}
      onChange={handleChange}
      fullWidth
      />
      <TextField
      className={classes.textField}
      label="Password"
      name="password"
      value={newUser.password}
      onChange={handleChange}
      fullWidth
      />
        <Button variant="contained" color="primary" onClick={onAdd}>Save</Button>{' '}
        <Button variant="outlined" onClick={handleCloseModal}>Cancel</Button>
      </div>
      </Modal>
     
      </>
       );
}

Gebruiker2.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(Gebruiker2);

Symfony 6 控制器代码

<?php 

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\PersonInfo;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class UserController extends AbstractController
{
    private $entityManager;
    
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    //function to retrieve the personInfo object from the database
    public function showAllUsersAndPersons()
    {
        // setting $personRepository to repository from the PersonInfo Class
        $personRepository = $this->entityManager->getRepository(PersonInfo::class);
        //in the personInfo Repo we run the findAll function which retrieves the data from the db
        $personList = $personRepository->findAll();
        
        // we set the data into array
        $data = array(
            'personInfo' => $personList
        );
        
        //return the array
        return $this->json($data);
    }

    public function searchUsers(Request $request, UserRepository $userRepository): JsonResponse
    {
        $requestData = json_decode($request->getContent(), true);
    
        $name = $requestData['username'] ?? null;
        $email = $requestData['email'] ?? null;
    
        if (!$name && !$email) {
            return new JsonResponse(['error' => 'At least one of username or email is required'], 400);
        }
    
        $users = $userRepository->createQueryBuilder('u');
    
        if ($name && !$email) {
            $users->where('u.username LIKE :name')
                ->setParameter('name', '%'.$name.'%');
        } elseif (!$name && $email) {
            $users->where('u.email LIKE :email')
                ->setParameter('email', '%'.$email.'%');
        } else {
            $users->where('u.username LIKE :name AND u.email LIKE :email')
                ->setParameter('name', '%'.$name.'%')
                ->setParameter('email', '%'.$email.'%');
        }
    
        $users = $users->getQuery()->getResult();
    
        if (empty($users)) {
            return new JsonResponse(['error' => 'No matching users found'], 404);
        }
    
        $responseData = [];
        foreach ($users as $user) {
            if ($name && !$email) {
                $responseData[] = [
                    'username' => $user->getUsername(),
                ];
            } elseif (!$name && $email) {
                $responseData[] = [
                    'email' => $user->getEmail(),
                ];
            } else {
                $responseData[] = [
                    'id' => $user->getId(),
                    'username' => $user->getUsername(),
                    'email' => $user->getEmail(),
                ];
            }
        }
    
        return $this->json($responseData);
    }
}
?>

问题原因

  • 响应格式不匹配:React初始加载用户列表时,Symfony接口返回的是{personInfo: [...]}格式,所以setUsers(data.personInfo)能正常工作;但搜索接口直接返回用户数组,没有外层的personInfo键,此时data.personInfo为undefined,调用setUsers(undefined)后,users变成undefined,后续渲染时users.map()就会触发TypeError。
  • 搜索返回字段不全:仅按用户名搜索时,Symfony返回的对象只有username字段,表格渲染时读取id、name等属性也会出错(当前报错由前者导致,此为次要问题)。

解决方案

方案一:统一Symfony接口响应格式

修改searchUsers方法的返回逻辑,将结果包裹在personInfo键下,和列表接口保持一致:

// 替换原return语句
return $this->json([
    'personInfo' => $responseData
]);

方案二:修改React搜索逻辑适配现有响应

调整handleSearch中的数据处理部分,直接使用返回的数组,并处理无结果的情况:

const handleSearch = () => {
    fetch(`https://127.0.0.1:8000/users/search`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: usernameSearch }),
    })
    .then(response => {
        console.log(response);
        return response.json();
    })
    .then(data => {
        if (Array.isArray(data)) {
            setUsers(data);
        } else if (data.error) {
            console.log(data.error);
            setUsers([]);
        } else {
            console.log("Invalid response format");
            setUsers([]);
        }
    })
    .catch(error => {
        console.log(error);
        setUsers([]);
    });
};

额外优化:确保搜索返回完整字段

修改Symfony的searchUsers方法,无论搜索条件如何,都返回表格需要的完整字段:

// 替换foreach循环部分
$responseData = [];
foreach ($users as $user) {
    $responseData[] = [
        'id' => $user->getId(),
        'username' => $user->getUsername(),
        'email' => $user->getEmail(),
        'name' => $user->getName() // 根据User实体实际方法调整
    ];
}

内容的提问来源于stack exchange,提问作者user20120520

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 19:37:03