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
相关产品推荐
相关产品推荐

