React Router v6函数传递报错:Uncaught TypeError: foo is not a function
React Router 提交函数传递失败问题
开发React Router项目时,无法将Form组件的订单提交函数传递给App组件,触发Uncaught TypeError: foo is not a function错误。尝试使用useLocation钩子接收App组件的props,但解构的props未被实际使用;同样的状态传递方式在Orders组件中可行,但Form组件无法正常工作。
文件结构
src: -- App.jsx -- Forms.jsx -- Orders.jsx
相关代码
App.jsx
import { useEffect, useState } from 'react'; import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'; import { Button, Typography } from '@mui/material'; import Form from './Form.jsx'; import Orders from './Orders.jsx'; import Error from './Error.jsx'; import './App.css'; const starterOrders = [ { firstName: 'Billy', lastName: 'Gibbons', orderId: 'ZZTop0000001', productName: 'Hat', quantity: 2, price: 14.99, }, { firstName: 'Dusty', lastName: 'Hill', orderId: 'ZZTop0000002', productName: 'Shirt', quantity: 1, price: 19.99, }, { firstName: 'Frank', lastName: 'Beard', orderId: 'ZZTop0000003', productName: 'Hat', quantity: 2, price: 29.98, }, { firstName: 'David', lastName: 'Bowie', orderId: 'UndrPr3ssr81', productName: 'Sweatshirt', quantity: 1, price: 29.98, }, ]; const navBackgroundStyles = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', backgroundColor: 'rgb(255, 255, 255)', }; function App() { const [orders, setOrders] = useState(starterOrders); useEffect(() => { const cachedData = window.localStorage.getItem('ORDERS_ARRAY'); if (cachedData?.length) { setOrders(JSON.parse(cachedData)); } }, []) const newOrderFunc = (order) => { setOrders((prevOrders) => { const newOrders = [order, ...prevOrders] window.localStorage.setItem("ORDERS_ARRAY", JSON.stringify(newOrders)); return newOrders; }); }; return ( <Router> <div style={navBackgroundStyles}> <div> <Typography sx={{ marginLeft: '1rem', fontSize: '2rem', color: 'rgb(33, 150, 243)', }} > Clothing Co. </Typography> </div> <div> <Link to="/" state={{ onSaveOrderData: newOrderFunc }}> <Button sx={{ marginRight: '1rem' }} variant="text"> Place Order </Button> </Link> <Link to="/orders" state={{ orders: orders }}> <Button sx={{ marginRight: '1rem' }} variant="text"> Orders </Button> </Link> </div> </div> <Routes> <Route path="/" element={<Form />} /> <Route path="/orders" element={<Orders />} /> <Route path="/*" element={<Error />} /> </Routes> </Router> ); } export default App;
Form.jsx
import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { Alert, Box, Button, InputAdornment, InputLabel, MenuItem, OutlinedInput, Select, Slide, TextField, } from '@mui/material'; export default function Form({ onSaveOrderData }) { const location = useLocation(); const saveFunction = location.state?.onSaveOrderData; const [isActive, setIsActive] = useState(false); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [product, setProduct] = useState(''); const [quantity, setQuantity] = useState(''); const [price, setPrice] = useState(''); const quantityMenu = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Generate random Order IDs const makeId = () => { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < 12; i++) { result += characters.charAt( Math.floor(Math.random() * characters.length) ); } return result; }; // Converts product value integer to product name const productName = () => { switch (product) { case 1: return 'Shirt'; case 2: return 'Hat'; case 3: return 'Sweatshirt'; } }; // Calculates order cost on change of product or quantity state useEffect(() => { if (quantity > 0) { switch (product) { case 1: setPrice((19.99 * quantity).toFixed(2)); break; case 2: setPrice((14.99 * quantity).toFixed(2)); break; case 3: setPrice((29.98 * quantity).toFixed(2)); break; } } }, [product, quantity]); // Sends new order to App, resets form fields const submitHandler = (e) => { e.preventDefault(); const newOrder = { firstName: firstName, lastName: lastName, orderId: makeId(), productName: productName(), quantity: quantity, price: price, }; setIsActive(true); saveFunction(newOrder); setFirstName(''); setLastName(''); setProduct(''); setQuantity(''); setPrice(''); }; return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '90vh' }} > <div style={{ position: 'fixed', top: '60px', right: '10px' }}> <Slide direction="left" in={isActive} mountOnEnter unmountOnExit> <Alert severity="success" onClose={() => setIsActive(false)} style={{ display: isActive ? 'flex' : 'none' }} > Thank you for your order! </Alert> </Slide> </div> <Box sx={{ padding: '2rem', width: 'fit-content', border: '1px solid rgb(148, 148, 148)', borderRadius: '5px', backgroundColor: 'rgb(255, 255, 255)', }} > <TextField label="First Name" variant="standard" fullWidth sx={{ marginRight: '5px' }} value={firstName} onChange={(e) => setFirstName(e.target.value)} /> <TextField label="Last Name" variant="standard" fullWidth sx={{ marginRight: '5px' }} value={lastName} onChange={(e) => setLastName(e.target.value)} /> <InputLabel style={{ display: 'block', margin: '1rem 0 0', color: 'rgb(102, 102, 102)', }} > Product </InputLabel> <Select fullWidth value={product} onChange={(e) => setProduct(e.target.value)} > <MenuItem value=""> <em>Select product</em> </MenuItem> <MenuItem value={1}>Shirt</MenuItem> <MenuItem value={2}>Hat</MenuItem> <MenuItem value={3}>Sweatshirt</MenuItem> </Select> <InputLabel style={{ display: 'inline-block', margin: '1rem 0 0', color: 'rgb(102, 102, 102)', }} > Quantity </InputLabel> <Select fullWidth value={quantity} onChange={(e) => setQuantity(e.target.value)} > <MenuItem value=""> <em>Select quantity</em> </MenuItem> {quantityMenu.map((item) => { return ( <MenuItem key={item} value={item}> {item} </MenuItem> ); })} </Select> <InputLabel htmlFor="outlined-adornment-amount" sx={{ margin: '1rem 0 0' }} > Amount </InputLabel> <OutlinedInput fullWidth value={price} startAdornment={<InputAdornment position="start">$</InputAdornment>} inputProps={{ readOnly: true }} /> <Button variant="outlined" fullWidth sx={{ marginTop: '1.25rem', fontSize: '1rem', color: 'dark', }} onClick={submitHandler} > Place Order </Button> </Box> </div> ); }
Orders.jsx
import React from 'react'; import { useLocation } from 'react-router-dom'; import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from '@mui/material'; export default function Orders({orders}) { const location = useLocation(); const ordersList = location.state?.orders; return ( <TableContainer component={Paper}> <Typography variant="h1">Orders</Typography> <Table sx={{ minWidth: 600 }} aria-label="simple table"> <TableHead> <TableRow> <TableCell>First Name</TableCell> <TableCell>Last Name</TableCell> <TableCell>Order ID</TableCell> <TableCell>Product Name</TableCell> <TableCell>Quantity</TableCell> <TableCell>Price</TableCell> </TableRow> </TableHead> <TableBody> {ordersList.map((order) => ( <TableRow key={order.orderId} sx={{ '&:last-child td, &:last-child th': { border: 0 } }} > <TableCell scope="row">{order.firstName}</TableCell> <TableCell scope="row">{order.lastName}</TableCell> <TableCell>{order.orderId}</TableCell> <TableCell>{order.productName}</TableCell> <TableCell>{order.quantity}</TableCell> <TableCell>{'$' + `${order.price}`}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ); }
问题分析
- 函数传递的局限性:通过
Link的state传递函数时,函数无法被序列化,若用户直接访问路由(如刷新页面),location.state会丢失,导致saveFunction为undefined,调用时触发错误。 - 未正确使用props:Form组件中声明了
onSaveOrderDataprops但未实际使用,而是依赖location.state;若用户直接进入根路径,location.state不会包含该函数,导致调用失败。
解决方案
方案1:直接通过props传递函数(推荐)
这种方式更可靠,不受路由访问方式影响:
- 修改App.jsx中的Route,直接传递函数作为props:
<Route path="/" element={<Form onSaveOrderData={newOrderFunc} />} />
- 修改Form.jsx,移除useLocation相关代码,直接使用props中的函数:
export default function Form({ onSaveOrderData }) { // 移除以下两行 // const location = useLocation(); // const saveFunction = location.state?.onSaveOrderData; // 在submitHandler中调用props中的函数 const submitHandler = (e) => { e.preventDefault(); const newOrder = { firstName: firstName, lastName: lastName, orderId: makeId(), productName: productName(), quantity: quantity, price: price, }; setIsActive(true); onSaveOrderData(newOrder); setFirstName(''); setLastName(''); setProduct(''); setQuantity(''); setPrice(''); }; }
方案2:修复useLocation传递逻辑(不推荐)
若坚持使用location.state,需在调用前检查函数是否存在,避免错误:
const submitHandler = (e) => { e.preventDefault(); const newOrder = { // ... 订单数据 }; setIsActive(true); // 先验证函数是否存在 if (typeof saveFunction === 'function') { saveFunction(newOrder); } else { console.error('请通过页面导航链接进入下单页面'); } // ... 重置表单 };
但此方式仍存在刷新页面丢失函数的问题,仅适用于内存内跳转场景。
内容的提问来源于stack exchange,提问作者Patrick Mahloy
相关产品推荐
相关产品推荐

