英文:
React/Props/State: Uncaught TypeError: Cannot read properties of null (reading 'userId') at Function.mapStateToProps
问题
Here's the translated code part:
我有一个正在开发的博客 Web 应用程序。
主题是名为“Bob's Garage”的汽车修理厂。
我有一个“博客”页面,其他页面基本上与“博客”页面相同,但内容不同(例如“用户反馈页面”、“用户评论页面”等)。
“博客”页面可以正常工作,但“反馈”和“评论”页面不行。
这里是代码:
AddFeedback.js:
```javascript
// 导入所需的模块和组件
import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { addFeedback } from '../../actions/feedbackActions';
// 组件定义
const AddFeedback = ({ addFeedback, userId }) => {
const [formData, setFormData] = useState({
Author: '',
Heading: '',
Feedback: '',
Location: '',
errors: {
body: '您必须在提供的文本框中输入您的反馈',
},
});
// 其余的代码...
return (
<Fragment>
{/* JSX 渲染部分 */}
</Fragment>
);
};
// PropTypes 部分
AddFeedback.propTypes = {
addFeedback: PropTypes.func.isRequired,
};
// mapStateToProps 部分
const mapStateToProps = (state) => ({
userId: state.auth.user.userId,
});
// 导出组件
export default connect(mapStateToProps, { addFeedback })(AddFeedback);
AddBlog.js:
// 导入所需的模块和组件
import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { addBlog } from '../../actions/blogActions';
// 组件定义
const AddBlog = ({ addBlog, userId }) => {
const [formData, setFormData] = useState({
Post: '',
Author: '',
Heading: '',
errors: {
Post: '您必须在提供的文本框中输入您的博客文章',
},
});
// 其余的代码...
return (
<Fragment>
{/* JSX 渲染部分 */}
</Fragment>
);
};
// PropTypes 部分
AddBlog.propTypes = {
addBlog: PropTypes.func.isRequired,
};
// mapStateToProps 部分
const mapStateToProps = (state) => ({
userId: state.auth.user.userId,
});
// 导出组件
export default connect(mapStateToProps, { addBlog })(AddBlog);
希望这有助于你解决问题。如果你需要更多帮助或其他源文件,请告诉我。
英文:
I have a blog webapp that I'm developing.
The theme is a car mechanic's workshop called "Bob's Garage".
I have a "blog" page, then the rest of the pages are basically the same as the "blog" page but with different content (e.g "User Feedback page". "User reviews page", etc..
The "blog" page is working, but the "feedback" and "review" page are not.
The code is virtually identical between the pages, just some different names (e.g "AddBlog()" becomes "AddFeedback()")
Here is the code:
AddFeedback.js:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
//rafcp
// import fragment, useState
import React, {Fragment, useState} from 'react';
import PropTypes from 'prop-types';
// import useNavigate
import {useNavigate} from 'react-router-dom';
// import connect
import { connect } from 'react-redux';
// import our addFeedback function.
import { addFeedback} from '../../actions/feedbackActions';
const AddFeedback = ({ addFeedback, userId }) => {
// Create component state.
const [formData, setFormData] = useState({
Author: '',
Heading:'',
Feedback:'',
Location:'',
errors: {
body: 'You must enter your feedback in the box provided'
}
});
console.log("formData.body = " + formData.body); // formData.body is the user input in the textbox of the Add New Blog Entry page
// Use destructuring to pull the variables out of our state.
const { Author, Heading, Feedback, Location, errors } = formData;
// Create our navigate variable.
const navigate = useNavigate();
// on Change function.
const onChange = e => {
setFormData({
...formData, [e.target.name]: e.target.value
});
}
// Create our on submit
const onSubmit = async(e) => {
e.preventDefault();
// Test that the onSubmit is called.
console.log('Onsumbit - Add running...');
// Check for errors / validation.
if( Feedback === '' || Feedback === undefined){
console.log('feedback empty');
// Save an error message to the state using the errors object.
// remember that we also need to include everything that is in the state.
setFormData({
...formData,
errors:{
body: 'You must enter feedback in the box above',
Heading: 'You must enter your heading in the box above',
Author: 'You must enter your name in the box above',
Feedback: 'You must enter your feedback in the box above',
Location: 'You must enter your location in the box above'
}
});
// stop the onSubmit running.
return;
} else {
setFormData({
...formData,
errors:{
Author: '',
Heading: '',
Feedback: '',
Location: '',
body: ''
}
});
}
let publishDate = new Date();
let publishDay = publishDate.getDate();
let publishMonth = publishDate.getMonth() + 1;
let publishYear = publishDate.getFullYear();
console.log("AddFeedback.js: userId = " + userId);
// create a newItem to add to our feedback list.
const newItem = {
Author: Author,
Heading: Heading,
Feedback: Feedback,
PublishDate: publishYear + "-" + publishMonth + "-" + publishDay,
Location: Location,
Id: userId,
UserUserId: userId
//UserUserId: userUserId
//UserUserId:auth.user.userId
}
console.log(newItem);
// Send our newItem to an API or state managemeht system.
// Call our addFeedback function.
addFeedback(newItem);
// We can do other things after this, like redirect the browser.
navigate('/feedback');
}; // end of onSubmit.
return (
<Fragment>
<h2 className='text-primary'>Add New Feedback</h2>
<div className='card mb-3'>
<div className='card-header'>
Add Your Feedback
</div>
<div className='card-body'>
<form onSubmit={e => onSubmit(e)}>
<div className='mb-3'>
<label htmlFor='body'>Heading:</label>
<textarea
className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
id='heading'
placeholder='Your Heading'
name='Heading'
value={Heading}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.heading && <div className='invalid-feedback'>
{errors.heading}
</div>}
</div>
<div className='mb-3'>
<label htmlFor='body'>Author:</label>
<textarea
className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
id='author'
placeholder='Author'
name='Author'
value={Author}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.heading && <div className='invalid-feedback'>
{errors.heading}
</div>}
</div>
<div className='mb-3'>
<label htmlFor='body'>Location:</label>
<textarea
className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
id='location'
placeholder='Location'
name='Location'
value={Location}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.heading && <div className='invalid-feedback'>
{errors.heading}
</div>}
</div>
<div className='mb-3'>
<label htmlFor='body'>Have your say below:</label>
<textarea
className={`form-control ${errors.body ? "is-invalid" : 'is-valid'}`}
id='feedback'
placeholder='Your Feedback'
name='Feedback'
value={Feedback}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.body && <div className='invalid-feedback'>
{errors.body}
</div>}
</div>
{/* <div className='mb-3'>
// <label htmlFor='name'>Name</label>
// <input
// type='text'
// className='form-control'
// id='name'
// placeholder='Name'
// name='name'
// value={name}
// onChange={e => onChange(e)}
// />
</div> */}
{/* <div className='mb-3'>
<label htmlFor='image'>Image</label>
<input
type='text'
className='form-control'
id='image'
placeholder='URL for image'
name='image'
value={image}
onChange={e => onChange(e)}
/>
</div> */}
<div className='d-grid gap-2'>
<input type='submit' value='Add Feedback' className='btn btn-light '/>
</div>
</form>
</div>
</div>
</Fragment>
)
}
// Proptypes
AddFeedback.propTypes = {
addFeedback: PropTypes.func.isRequired
}
//mapState to props
//user from state(auth)
//get the user id of the person logged in
//auth.user
//auth.user.userId
//pass that userId through in onSubmit function
const mapStateToProps = state => ({
userId: state.auth.user.userId
})
// add in connect.
// We do not need anything from the state or store.
// that means null, as we do not need mapState to props.
export default connect(mapStateToProps, {addFeedback})(AddFeedback)
<!-- end snippet -->
AddBlog.js:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
//rafcp
// import fragment, useState
import React, {Fragment, useState} from 'react';
import PropTypes from 'prop-types';
// import useNavigate
import {useNavigate} from 'react-router-dom';
// import connect
import { connect } from 'react-redux';
// import our addFeedback function.
import { addBlog} from '../../actions/blogActions';
const AddBlog = ({ addBlog, userId }) => {
// Create component state.
const [formData, setFormData] = useState({
//Post: '',
//Author: '',
Heading: '',
errors: {
Post: 'You must enter your blog post in the box provided'
}
});
console.log("formData.Post = " + formData.Post); // formData.Post is the user input in the textbox of the Add New Blog Entry page
// Use destructuring to pull the variables out of our state.
const { Post, Author, ImageURL, errors, Heading } = formData;
// Create our navigate variable.
const navigate = useNavigate();
// on Change function.
const onChange = e => {
setFormData({
...formData, [e.target.name]: e.target.value
});
}
// Create our on submit
const onSubmit = async(e) => {
e.preventDefault();
// Test that the onSubmit is called.
console.log('Onsumbit - Add running...');
// Check for errors / validation.
if( Post === '' || Post === undefined){
console.log('post empty');
// Save an error message to the state using the errors object.
// remember that we also need to include everything that is in the state.
setFormData({
...formData,
errors:{
Post: 'You must enter blog entry in the box above'
}
});
// stop the onSubmit running.
return;
} else {
setFormData({
...formData,
errors:{
Post: ''
}
});
}
let publishDate = new Date();
let publishDay = publishDate.getDate();
let publishMonth = publishDate.getMonth() + 1;
let publishYear = publishDate.getFullYear();
console.log("AddBlog.js: userId = " + userId);
// create a newItem to add to our feedback list.
const newItem = {
Post: Post,
Author: Author,
Heading: Heading,
ImageURL: ImageURL,
PublishDate: publishYear + "-" + publishMonth + "-" + publishDay, // create function
UserUserId: userId
//UserUserId: userUserId
//UserUserId:auth.user.userId
}
console.log(newItem);
// Send our newItem to an API or state managemeht system.
// Call our addFeedback function.
addBlog(newItem);
// We can do other things after this, like redirect the browser.
navigate('/blog');
}; // end of onSubmit.
return (
<Fragment>
<h2 className='text-primary'>Add New Blog Entry</h2>
<div className='card mb-3'>
<div className='card-header'>
Add Your Blog Entry
</div>
<div className='card-body'>
<form onSubmit={e => onSubmit(e)}>
<div className='mb-3'>
{/* Heading */}
<label htmlFor='body'>Heading:</label>
<textarea
className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
id='heading'
placeholder='Your Heading'
name='Heading'
value={Heading}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.heading && <div className='invalid-feedback'>
{errors.heading}
</div>}
</div>
{/* Image URL */}
<div className='mb-3'>
<label htmlFor='body'>Image URL:</label>
<textarea
className={`form-control ${errors.ImageURL ? "is-invalid" : 'is-valid'}`}
id='imageurl'
placeholder='Your image URL'
name='ImageURL'
value={ImageURL}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.ImageURL && <div className='invalid-feedback'>
{errors.ImageURL}
</div>}
</div>
{/* Author */}
<div className='mb-3'>
<label htmlFor='body'>Author:</label>
<textarea
className={`form-control ${errors.Author ? "is-invalid" : 'is-valid'}`}
id='author'
placeholder='Your Heading'
name='Author'
value={Author}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.heading && <div className='invalid-feedback'>
{errors.heading}
</div>}
</div>
{/* Author */}
<div className='mb-3'>
<label htmlFor='body'>Have your say below:</label>
<textarea
className={`form-control ${errors.Post ? "is-invalid" : 'is-valid'}`}
id='post'
placeholder='Your Blog Post'
name='Post'
value={Post}
// Add in our onChange event
onChange={ e => onChange(e)}
></textarea>
{errors.Post && <div className='invalid-feedback'>
{errors.Post}
</div>}
</div>
{/* <div className='mb-3'>
// <label htmlFor='name'>Name</label>
// <input
// type='text'
// className='form-control'
// id='name'
// placeholder='Name'
// name='name'
// value={name}
// onChange={e => onChange(e)}
// />
</div> */}
{/* <div className='mb-3'>
<label htmlFor='image'>Image</label>
<input
type='text'
className='form-control'
id='image'
placeholder='URL for image'
name='image'
value={image}
onChange={e => onChange(e)}
/>
</div> */}
<div className='d-grid gap-2'>
<input type='submit' value='Add Blog Entry' className='btn btn-light '/>
</div>
</form>
</div>
</div>
</Fragment>
)
}
// Proptypes
AddBlog.propTypes = {
addBlog: PropTypes.func.isRequired
}
//mapState to props
//user from state(auth)
//get the user id of the person logged in
//auth.user
//auth.user.userId
//pass that userId through in onSubmit function
const mapStateToProps = state => ({
userId: state.auth.user.userId
})
// add in connect.
// We do not need anything from the state or store.
// that means null, as we do not need mapState to props.
export default connect(mapStateToProps, {addBlog})(AddBlog)
<!-- end snippet -->
I cannot understand why 'userId' is evaluating to 'null'...
Do any of you have any ideas as to why this error might be occurring?
Here is a screenshot of the Redux Dev Tools 'State' windows whilst on the 'AddFeedback.js' page.
Please let me know if you need me to post up any other source files, such as Context.js or similar.
Kind Regards,
John
Melbourne, Australia
答案1
得分: 0
我找到了解决我的问题的方法。
作为React的新手,我无意中使用了< a >标签来导航到AddFeedback.js页面,这导致状态丢失,这就是为什么userId变成了null。
所以,对于所有的React新手:
请使用React的< Link to=/your/path/here >您的链接文本< / Link>。
亲切的问候,
约翰
澳大利亚墨尔本
英文:
I found the solution to my problem.
As a React newbie I had inadvertently used an < a > tag to navigate to the AddFeedback.js page, which caused state to be lost, which is why userId became null.
So, to all you React newbies out there:
Use a React < Link to=/your/path/here > Your link text < / Link>
Kind Regards,
John
Melbourne, Australia
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论