英文:
Uncaught (in promise) Error: Invalid hook call on fetch()
问题
CreateBlogPost.js中的代码有一个问题,导致出现“Invalid hook call”的错误。这是因为您在函数组件之外使用了React的Hooks。Hooks只能在函数组件的内部使用,而不能在函数外部或类组件中使用。
要解决这个问题,您需要将handlePostButtonPressed
函数中的Hooks调用移到函数组件的内部。以下是修改后的代码:
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { FormControl, FormHelperText, TextField, Typography, Button } from "@mui/material";
import { Link } from "react-router-dom";
import { Navigate } from "react-router-dom";
export default function CreateBlogPost(props) {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [tags, setTags] = useState('');
const handleTitle = event => {
setTitle(event.target.value);
}
const handleContent = event => {
setContent(event.target.value);
}
const handleTags = event => {
setTags(event.target.value);
}
const handlePostButtonPressed = () => { // Remove the parameter
const requestOptions = {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: title,
content: content,
tags: tags,
})
};
console.log(requestOptions);
fetch("/blog/create", requestOptions)
.then((response) => response.json())
.then((data) => Navigate('/posts/' + data.postid));
}
return (
<Grid container spacing={1} className="pl-2 pt-4">
<Grid item xs={12}>
<Typography component="h4" variant="h4">Create a post</Typography>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Title:</div>
</FormHelperText>
<FormControl>
<TextField required={true} type="text" value={title} onChange={handleTitle}/>
</FormControl>
</Grid>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Content:</div>
</FormHelperText>
<FormControl component="fieldset">
<TextField required={true} type="text" value={content} onChange={handleContent}/>
</FormControl>
</Grid>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Tags:</div>
</FormHelperText>
<FormControl component="fieldset">
<TextField required={true} type="text" value={tags} onChange={handleTags}/>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button color="primary" variant="contained" onClick={handlePostButtonPressed}>
Post
</Button>
<Button color="secondary" variant="contained" to="/blogs/" component={Link}>
Back
</Button>
</Grid>
</Grid>
)
}
通过将handlePostButtonPressed
函数中的Hooks调用移动到函数组件内部,您应该能够解决“Invalid hook call”错误。这样,您的代码应该可以正常工作了。
英文:
I'm working on a webapp for Django. It is a simple blog. The front end is being built in react and materials UI, it is based off of the tutorial here, but I have had to adapt much by myself because he is using a Class-oriented approach many methods of which are deprecated as React seems to be moving towards a function based model. In the model that the demonstrator created, we are passing data from our react frontend to our django backend by use of generating JSON objects which python serializes. I have built a blog post submission form that doesn't work for a reason I can't understand.
CreateBlogPost.js
import React, { Component } from "react";
import Grid from "@mui/material/Grid";
import { FormControl, FormHelperText, TextField, Typography, Button } from "@mui/material";
import { Link } from "react-router-dom";
import { useState } from "react";
import { Navigate } from "react-router-dom";
export default function CreateBlogPost(props) {
const[title,setTitle] = useState('');
const[content,setContent] = useState('');
const[tags, setTags] = useState('');
const handleTitle = event => {
setTitle(event.target.value);
}
const handleContent = event => {
setContent(event.target.value);
}
const handleTags = event => {
setTags(event.target.value);
}
const requestOptions = {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: title,
content: content,
tags: tags,
})
};
const handlePostButtonPressed = (requestOptions) => {
console.log(requestOptions)
fetch("/blog/create",requestOptions)
.then((response) => response.json())
.then((data) => Navigate('/posts/' + data.postid));
}
return (
<Grid container spacing={1} className="pl-2 pt-4">
<Grid item xs={12}>
<Typography component="h4" variant="h4">Create a post</Typography>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Title:</div>
</FormHelperText>
<FormControl>
<TextField required={true} type="text" value={title} onChange={handleTitle}/>
</FormControl>
</Grid>
</Grid>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Content:</div>
</FormHelperText>
<FormControl component="fieldset">
<TextField required={true} type="text" value={content} onChange={handleContent}/>
</FormControl>
</Grid>
<Grid item xs={12} className="-ml-2 mt-4">
<FormHelperText>
<div>Tags:</div>
</FormHelperText>
<FormControl component="fieldset">
<TextField required={true} type="text" value={tags} onChange={handleTags}/>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button color="primary" variant="contained" onClick={handlePostButtonPressed}>
Post
</Button>
<Button color="secondary" variant="contained" to="/blogs/" component={Link}>
Back
</Button>
</Grid>
</Grid>
)
}
Everything in this works fine until I press the Post button. In the log I see the JSON object of the post I want to submit properly rendered (so I know this is not an error with any of the data handler functions), but I get an error where the fetch function occurs:
"Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
- You might have mismatching versions of React and the renderer (such as React DOM)
- You might be breaking the Rules of Hooks
- You might have more than one copy of React in the same app"
I do not believe I have mismatching versions of React. Here is my package.json:
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack watch --mode development ",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.20.7",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.0",
"glob-all": "^3.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.2.4",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.3",
"autoprefixer": "^10.4.13",
"css-loader": "^6.7.3",
"draft-js": "^0.11.7",
"postcss-cli": "^10.1.0",
"postcss-loader": "^7.0.2",
"react-draft-wysiwyg": "^1.15.0",
"react-router-dom": "^6.6.1",
"style-loader": "^3.3.1"
}
}
I have researched this problem and have not been able to find a solution. I have found other ways of building forms in react, but that would change how my backend receives the data. I would prefer to avoid that unless absolutely necessary.
答案1
得分: 0
问题
代码导入了 Navigate
组件并尝试直接调用它。
import { Navigate } from "react-router-dom";
export default function CreateBlogPost(props) {
...
const handlePostButtonPressed = (requestOptions) => {
console.log(requestOptions)
fetch("/blog/create", requestOptions)
.then((response) => response.json())
.then((data) => Navigate('/posts/' + data.postid)); // <-- invoked here
};
...
在 React 中,我们不直接调用/调用 React 组件,而是在 React 组件的渲染返回中 以 JSX 形式 渲染它们,即 <Navigate to="..." />
。通过直接将 React 组件 作为函数 调用,您正在 违反 hooks 规则(间接地)。
Navigate
是一个组件,当 渲染 时,会产生一个 声明式 导航操作,即导航到指定的路由路径。我确信您打算导入 useNavigate
钩子来使用 navigate
函数,并在处理程序中执行 命令式 导航操作。
解决方案
导入 useNavigate
并发出命令式导航操作。删除 requestOptions
参数,并使用在函数组件作用域中声明的 requestOptions
。这是因为 handlePostButtonPressed
直接传递为事件处理程序。
import { useNavigate } from "react-router-dom";
export default function CreateBlogPost(props) {
const navigate = useNavigate();
...
const requestOptions = {
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
content: content,
tags: tags,
})
};
const handlePostButtonPressed = () => {
console.log(requestOptions)
fetch("/blog/create", requestOptions)
.then((response) => response.json())
.then((data) => navigate(`/posts/${data.postid}`);
};
...
<Button
color="primary"
variant="contained"
onClick={handlePostButtonPressed}
>
Post
</Button>
...
}
英文:
Issue
The code imports the Navigate
component and tries to directly invoke it.
import { Navigate } from "react-router-dom";
export default function CreateBlogPost(props) {
...
const handlePostButtonPressed = (requestOptions) => {
console.log(requestOptions)
fetch("/blog/create", requestOptions)
.then((response) => response.json())
.then((data) => Navigate('/posts/' + data.postid)); // <-- invoked here
};
...
In React we don't directly call/invoke React components, we render them as JSX in the render return of a React component, i.e. <Navigate to="..." />
. By directly calling the React component as a function you are breaking the rules of hooks (indirectly).
Navigate
is a component that, when rendered, effects a declarative navigate action, i.e. navigates to the specified route path. I'm certain you meant to import the useNavigate
hook to use the navigate
function and effect an imperative navigation action in the handler.
Solution
Import useNavigate
and issue an imperative navigate action. Remove the requestOptions
arg and use the requestOptions
that is declared in function component scope. This is because handlePostButtonPressed
is passed directly as an event handler.
import { useNavigate } from "react-router-dom";
export default function CreateBlogPost(props) {
const navigate = useNavigate();
...
const requestOptions = {
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
content: content,
tags: tags,
})
};
const handlePostButtonPressed = () => {
console.log(requestOptions)
fetch("/blog/create", requestOptions)
.then((response) => response.json())
.then((data) => navigate(`/posts/${data.postid}`);
};
...
<Button
color="primary"
variant="contained"
onClick={handlePostButtonPressed}
>
Post
</Button>
...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论