函数之间的冲突,api 没有命中
Clash between functions, api not hitting
我正在使用 MERN 堆栈和 Redux。我创建了一个函数来更新数据库中的 属性。我已经在 Postman 上测试了 api 并且它有效。当我尝试 运行 时,它似乎与另一个函数发生冲突,并且我收到错误 'TypeError: this.props.subjects.map is not a function',该错误在调用 trueVote 函数之前起作用。有人知道我在这里缺少什么吗?
打印输出显示 action 和 reducer 被击中,但 api 未被击中,即使这适用于 Postman。正在从 voteHandler
中调用该函数
编辑:api 中的 console.log 消息不打印,但在 VS 上的终端 window 中显示,当我在错误发生后刷新我的页面时该函数做了什么应该是,相关数据已经更新。这是管理错误的情况吗?如果是这样,我该怎么做才不会使应用程序崩溃?
api
// put req for a true vote
subjectRouter.put("subject/true/:_id/:currTrue", (req, res) => {
console.log("True api hitting");
Subject.findOneAndUpdate(
{ _id: req.params._id },
{
true: Number(req.params.currTrue) + 1,
},
{
new: true,
useFindAndModify: false,
}
)
.then((subject) => res.json(subject))
.catch((err) => console.log(err));
});
行动
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subjects: subject,
})
);
};
减速器
case TRUE_VOTE:
console.log("true reducer hitting");
return {
...state,
items: action.subjects,
};
分量
import React, { Component } from "react";
import PropTypes from "prop-types";
import GoogleSearch from "./GoogleSearch";
import { connect } from "react-redux";
import { fetchLatestSubjects } from "../../actions/subject";
import { fetchTopicSubjects } from "../../actions/subject";
import { fetchTopicComments } from "../../actions/comment";
import { fetchComments } from "../../actions/comment";
import { rateSubject } from "../../actions/subject";
import { fetchUsers } from "../../actions/authActions";
import { rateUser } from "../../actions/authActions";
import { rateComment } from "../../actions/comment";
import { trueVote } from "../../actions/subject";
import { falseVote } from "../../actions/subject";
class Subject extends Component {
// on loading the subjects and comments
// are fetched from the database
componentDidMount() {
this.props.fetchLatestSubjects();
this.props.fetchComments();
this.props.fetchUsers();
}
constructor(props) {
super(props);
this.state = {
// set inital state for subjects
// description, summary and comments all invisible
viewDesription: -1,
viewSummary: -1,
comments: [],
topic: "subjects",
};
}
componentWillReceiveProps(nextProps) {
// new subject and comments are added to the top
// of the arrays
if (nextProps.newPost) {
this.props.subjects.unshift(nextProps.newPost);
}
if (nextProps.newPost) {
this.props.comments.unshift(nextProps.newPost);
}
}
clickHandler = (id) => {
// when a subject title is clicked pass in its id
const { viewDescription } = this.state;
this.setState({ comments: [] });
var temp = [];
// get the details of the author of the subject and save to state
const subject = this.props.subjects.find((subject) => subject._id === id);
const user = this.props.users.find((user) => user._id === subject.author);
// save comments for subject to temp array
var i;
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject === id) {
temp.unshift(this.props.comments[i]);
}
}
console.log(temp);
// for each comment add a property with the authors name
temp.forEach((comment) => {
var commentAuthor = this.props.users.find(
(user) => user._id === comment.author
);
comment.authName = commentAuthor.name;
});
// save the subject id to local storage
// this is done incase a new comment is added
// then the subject associated with it can be retrieved
// and added as a property of that comment
localStorage.setItem("passedSubject", id);
localStorage.setItem("passedTopic", subject.topic);
// add all changes to the state
this.setState({
viewDescription: viewDescription === id ? -1 : id,
comments: temp,
subAuthor: user.name,
authRating: user.rating,
authNoOfVotes: user.noOfVotes,
});
};
// hovering on and off subjects toggles the visibility of the summary
hoverHandler = (id) => {
this.setState({ viewSummary: id });
};
hoverOffHandler = () => {
this.setState({ viewSummary: -1 });
};
rateHandler = (id, rate, item) => {
if (item === "subject") {
// this function rates the subject and the author
const subject = this.props.subjects.find((subject) => subject._id === id);
const author = this.props.users.find(
(user) => user._id === subject.author
);
// call the rateSubject and rateUser functions
this.props.rateSubject(id, rate, subject.noOfVotes, subject.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this subject.");
} else if (item === "comment") {
const comment = this.props.comments.find((comment) => comment._id === id);
const author = this.props.users.find(
(user) => user._id === comment.author
);
// call the rateComment and rateUser functions
this.props.rateComment(id, rate, comment.noOfVotes, comment.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this comment.");
}
};
voteHandler = (id, currVote, vote) => {
if (vote == "True") {
console.log(id, currVote, vote);
this.props.trueVote(id, currVote);
} else if (vote == "False") {
console.log(id, currVote, vote);
this.props.falseVote(id, currVote);
}
};
render() {
const subjectItems = this.props.subjects.map((subject) => {
// if the state equals the id set to visible if not set to invisible
var view = this.state.viewDescription === subject._id ? "" : "none";
var hover = this.state.viewSummary === subject._id ? "" : "none";
var comments = this.state.comments;
var subjectAuthor = this.state.subAuthor;
var authRating = this.state.authRating;
var authNoOfVotes = this.state.authNoOfVotes;
var className = "";
if (subject.category === "Education") {
className = "Education";
} else if (subject.category === "Environment") {
className = "Environment";
} else if (subject.category === "Politics") {
className = "Politics";
} else if (subject.category === "Health") {
className = "Health";
} else if (subject.category === "Other") {
className = "Other";
}
return (
<div key={subject._id}>
<div
className={className}
onMouseEnter={() => this.hoverHandler(subject._id)}
onMouseLeave={() => this.hoverOffHandler()}
>
<p className="title" onClick={() => this.clickHandler(subject._id)}>
{subject.title}
</p>
<p className="vote" style={{ textAlign: "Right" }}>
True:{" "}
{((100 / (subject.true + subject.false)) * subject.true).toFixed(
1
)}
% {" False: "}
{((100 / (subject.true + subject.false)) * subject.false).toFixed(
1
)}
%
</p>
<p className="summary" style={{ display: hover }}>
{subject.summary}
</p>
</div>
<div className="subjectBody " style={{ display: view }}>
<div className="leftSubjectBody">
<div className="subjectAuthor">
<p className="author">
Subject created by: {subjectAuthor} -{" "}
{(authRating / authNoOfVotes).toFixed(1)}/5 Star user
{/* <br /> {subject.date} */}
</p>
</div>
<div className="subjectDescription">
<p className="description">{subject.description}</p>
</div>
<div className="subjectLinks">Links: {subject.links}</div>
</div>
<div className="rightSubjectBody">
<div className="rate">
<p> Rate this subject:</p>
<br />
<button
onClick={() => this.rateHandler(subject._id, 1, "subject")}
>
1
</button>
<button
onClick={() => this.rateHandler(subject._id, 2, "subject")}
>
2
</button>
<button
onClick={() => this.rateHandler(subject._id, 3, "subject")}
>
3
</button>
<button
onClick={() => this.rateHandler(subject._id, 4, "subject")}
>
4
</button>
<button
onClick={() => this.rateHandler(subject._id, 5, "subject")}
>
5
</button>
<p>
Rating: {(subject.rating / subject.noOfVotes).toFixed(1)}/5
</p>
</div>
<div className="voting">
<p>
Do you think this subject question is true or false based on
the evidence provided and your own reseach in the area? <br />
</p>
<p>Please vote and leave comments.</p>
<br />
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.true, "True")
}
>
TRUE
</div>
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.false, "False")
}
>
FALSE
</div>
</div>
</div>
<div className="subjectComments">
<p style={{ fontWeight: "bold" }}>Comments:</p>
{comments.map((comment, i) => {
return (
<div key={i} className="singleComment">
<p>
{comment.title}
<br />
{comment.comment}
<br />
Comment by : {comment.authName} - This user has a rating
of {(comment.rating / comment.noOfVotes).toFixed(1)}/5
STARS
</p>
<div className="rate">
Rate this comment:
<button
onClick={() =>
this.rateHandler(comment._id, 1, "comment")
}
>
1
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 2, "comment")
}
>
2
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 3, "comment")
}
>
3
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 4, "comment")
}
>
4
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 5, "comment")
}
>
5
</button>
<p>
Rating:{" "}
{(comment.rating / comment.noOfVotes).toFixed(1)}/5
</p>
</div>
</div>
);
})}
<br />
<a href="/addcomment">
<div className="buttonAddComment">ADD COMMENT</div>
</a>
</div>
</div>
</div>
);
});
return (
<div id="Subject">
<GoogleSearch />
{subjectItems}
</div>
);
}
}
Subject.propTypes = {
fetchLatestSubjects: PropTypes.func.isRequired,
fetchTopicSubjects: PropTypes.func.isRequired,
fetchTopicComments: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
fetchUsers: PropTypes.func.isRequired,
rateSubject: PropTypes.func.isRequired,
rateComment: PropTypes.func.isRequired,
rateUser: PropTypes.func.isRequired,
trueVote: PropTypes.func.isRequired,
falseVote: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
users: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
subjects: state.subjects.items,
newSubject: state.subjects.item,
comments: state.comments.items,
users: state.auth.users,
newComment: state.comments.item,
});
// export default Subject;
export default connect(mapStateToProps, {
fetchLatestSubjects,
fetchTopicSubjects,
fetchTopicComments,
fetchComments,
fetchUsers,
rateSubject, // rate subject
rateUser,
rateComment,
trueVote,
falseVote,
})(Subject, Comment);
因为您要返回单个 subject
作为缩减程序操作的一部分,您可能想用更新后的 subject
交换现有的 subject
,因此您需要更新你的减速机是:
case TRUE_VOTE:
const index = state.items.subjects.findIndex( subject => action.subjects.id === subject.id );
return {
items: [...state.items.slice(0, index), action.subjects, ...state.items.slice( index + 1 )] };
};
为了更清楚一点,您当然也可以更改您的操作,以表明您返回的只是一个主题
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subject
})
);
};
在你的 reducer 中你会更清楚你只需要 1 个主题
我正在使用 MERN 堆栈和 Redux。我创建了一个函数来更新数据库中的 属性。我已经在 Postman 上测试了 api 并且它有效。当我尝试 运行 时,它似乎与另一个函数发生冲突,并且我收到错误 'TypeError: this.props.subjects.map is not a function',该错误在调用 trueVote 函数之前起作用。有人知道我在这里缺少什么吗?
打印输出显示 action 和 reducer 被击中,但 api 未被击中,即使这适用于 Postman。正在从 voteHandler
中调用该函数编辑:api 中的 console.log 消息不打印,但在 VS 上的终端 window 中显示,当我在错误发生后刷新我的页面时该函数做了什么应该是,相关数据已经更新。这是管理错误的情况吗?如果是这样,我该怎么做才不会使应用程序崩溃?
api
// put req for a true vote
subjectRouter.put("subject/true/:_id/:currTrue", (req, res) => {
console.log("True api hitting");
Subject.findOneAndUpdate(
{ _id: req.params._id },
{
true: Number(req.params.currTrue) + 1,
},
{
new: true,
useFindAndModify: false,
}
)
.then((subject) => res.json(subject))
.catch((err) => console.log(err));
});
行动
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subjects: subject,
})
);
};
减速器
case TRUE_VOTE:
console.log("true reducer hitting");
return {
...state,
items: action.subjects,
};
分量
import React, { Component } from "react";
import PropTypes from "prop-types";
import GoogleSearch from "./GoogleSearch";
import { connect } from "react-redux";
import { fetchLatestSubjects } from "../../actions/subject";
import { fetchTopicSubjects } from "../../actions/subject";
import { fetchTopicComments } from "../../actions/comment";
import { fetchComments } from "../../actions/comment";
import { rateSubject } from "../../actions/subject";
import { fetchUsers } from "../../actions/authActions";
import { rateUser } from "../../actions/authActions";
import { rateComment } from "../../actions/comment";
import { trueVote } from "../../actions/subject";
import { falseVote } from "../../actions/subject";
class Subject extends Component {
// on loading the subjects and comments
// are fetched from the database
componentDidMount() {
this.props.fetchLatestSubjects();
this.props.fetchComments();
this.props.fetchUsers();
}
constructor(props) {
super(props);
this.state = {
// set inital state for subjects
// description, summary and comments all invisible
viewDesription: -1,
viewSummary: -1,
comments: [],
topic: "subjects",
};
}
componentWillReceiveProps(nextProps) {
// new subject and comments are added to the top
// of the arrays
if (nextProps.newPost) {
this.props.subjects.unshift(nextProps.newPost);
}
if (nextProps.newPost) {
this.props.comments.unshift(nextProps.newPost);
}
}
clickHandler = (id) => {
// when a subject title is clicked pass in its id
const { viewDescription } = this.state;
this.setState({ comments: [] });
var temp = [];
// get the details of the author of the subject and save to state
const subject = this.props.subjects.find((subject) => subject._id === id);
const user = this.props.users.find((user) => user._id === subject.author);
// save comments for subject to temp array
var i;
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject === id) {
temp.unshift(this.props.comments[i]);
}
}
console.log(temp);
// for each comment add a property with the authors name
temp.forEach((comment) => {
var commentAuthor = this.props.users.find(
(user) => user._id === comment.author
);
comment.authName = commentAuthor.name;
});
// save the subject id to local storage
// this is done incase a new comment is added
// then the subject associated with it can be retrieved
// and added as a property of that comment
localStorage.setItem("passedSubject", id);
localStorage.setItem("passedTopic", subject.topic);
// add all changes to the state
this.setState({
viewDescription: viewDescription === id ? -1 : id,
comments: temp,
subAuthor: user.name,
authRating: user.rating,
authNoOfVotes: user.noOfVotes,
});
};
// hovering on and off subjects toggles the visibility of the summary
hoverHandler = (id) => {
this.setState({ viewSummary: id });
};
hoverOffHandler = () => {
this.setState({ viewSummary: -1 });
};
rateHandler = (id, rate, item) => {
if (item === "subject") {
// this function rates the subject and the author
const subject = this.props.subjects.find((subject) => subject._id === id);
const author = this.props.users.find(
(user) => user._id === subject.author
);
// call the rateSubject and rateUser functions
this.props.rateSubject(id, rate, subject.noOfVotes, subject.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this subject.");
} else if (item === "comment") {
const comment = this.props.comments.find((comment) => comment._id === id);
const author = this.props.users.find(
(user) => user._id === comment.author
);
// call the rateComment and rateUser functions
this.props.rateComment(id, rate, comment.noOfVotes, comment.rating);
this.props.rateUser(author._id, rate, author.noOfVotes, author.rating);
console.log(author.name);
alert("Thank you for rating this comment.");
}
};
voteHandler = (id, currVote, vote) => {
if (vote == "True") {
console.log(id, currVote, vote);
this.props.trueVote(id, currVote);
} else if (vote == "False") {
console.log(id, currVote, vote);
this.props.falseVote(id, currVote);
}
};
render() {
const subjectItems = this.props.subjects.map((subject) => {
// if the state equals the id set to visible if not set to invisible
var view = this.state.viewDescription === subject._id ? "" : "none";
var hover = this.state.viewSummary === subject._id ? "" : "none";
var comments = this.state.comments;
var subjectAuthor = this.state.subAuthor;
var authRating = this.state.authRating;
var authNoOfVotes = this.state.authNoOfVotes;
var className = "";
if (subject.category === "Education") {
className = "Education";
} else if (subject.category === "Environment") {
className = "Environment";
} else if (subject.category === "Politics") {
className = "Politics";
} else if (subject.category === "Health") {
className = "Health";
} else if (subject.category === "Other") {
className = "Other";
}
return (
<div key={subject._id}>
<div
className={className}
onMouseEnter={() => this.hoverHandler(subject._id)}
onMouseLeave={() => this.hoverOffHandler()}
>
<p className="title" onClick={() => this.clickHandler(subject._id)}>
{subject.title}
</p>
<p className="vote" style={{ textAlign: "Right" }}>
True:{" "}
{((100 / (subject.true + subject.false)) * subject.true).toFixed(
1
)}
% {" False: "}
{((100 / (subject.true + subject.false)) * subject.false).toFixed(
1
)}
%
</p>
<p className="summary" style={{ display: hover }}>
{subject.summary}
</p>
</div>
<div className="subjectBody " style={{ display: view }}>
<div className="leftSubjectBody">
<div className="subjectAuthor">
<p className="author">
Subject created by: {subjectAuthor} -{" "}
{(authRating / authNoOfVotes).toFixed(1)}/5 Star user
{/* <br /> {subject.date} */}
</p>
</div>
<div className="subjectDescription">
<p className="description">{subject.description}</p>
</div>
<div className="subjectLinks">Links: {subject.links}</div>
</div>
<div className="rightSubjectBody">
<div className="rate">
<p> Rate this subject:</p>
<br />
<button
onClick={() => this.rateHandler(subject._id, 1, "subject")}
>
1
</button>
<button
onClick={() => this.rateHandler(subject._id, 2, "subject")}
>
2
</button>
<button
onClick={() => this.rateHandler(subject._id, 3, "subject")}
>
3
</button>
<button
onClick={() => this.rateHandler(subject._id, 4, "subject")}
>
4
</button>
<button
onClick={() => this.rateHandler(subject._id, 5, "subject")}
>
5
</button>
<p>
Rating: {(subject.rating / subject.noOfVotes).toFixed(1)}/5
</p>
</div>
<div className="voting">
<p>
Do you think this subject question is true or false based on
the evidence provided and your own reseach in the area? <br />
</p>
<p>Please vote and leave comments.</p>
<br />
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.true, "True")
}
>
TRUE
</div>
<div
className="voteButton"
onClick={() =>
this.voteHandler(subject._id, subject.false, "False")
}
>
FALSE
</div>
</div>
</div>
<div className="subjectComments">
<p style={{ fontWeight: "bold" }}>Comments:</p>
{comments.map((comment, i) => {
return (
<div key={i} className="singleComment">
<p>
{comment.title}
<br />
{comment.comment}
<br />
Comment by : {comment.authName} - This user has a rating
of {(comment.rating / comment.noOfVotes).toFixed(1)}/5
STARS
</p>
<div className="rate">
Rate this comment:
<button
onClick={() =>
this.rateHandler(comment._id, 1, "comment")
}
>
1
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 2, "comment")
}
>
2
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 3, "comment")
}
>
3
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 4, "comment")
}
>
4
</button>
<button
onClick={() =>
this.rateHandler(comment._id, 5, "comment")
}
>
5
</button>
<p>
Rating:{" "}
{(comment.rating / comment.noOfVotes).toFixed(1)}/5
</p>
</div>
</div>
);
})}
<br />
<a href="/addcomment">
<div className="buttonAddComment">ADD COMMENT</div>
</a>
</div>
</div>
</div>
);
});
return (
<div id="Subject">
<GoogleSearch />
{subjectItems}
</div>
);
}
}
Subject.propTypes = {
fetchLatestSubjects: PropTypes.func.isRequired,
fetchTopicSubjects: PropTypes.func.isRequired,
fetchTopicComments: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
fetchUsers: PropTypes.func.isRequired,
rateSubject: PropTypes.func.isRequired,
rateComment: PropTypes.func.isRequired,
rateUser: PropTypes.func.isRequired,
trueVote: PropTypes.func.isRequired,
falseVote: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
users: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
subjects: state.subjects.items,
newSubject: state.subjects.item,
comments: state.comments.items,
users: state.auth.users,
newComment: state.comments.item,
});
// export default Subject;
export default connect(mapStateToProps, {
fetchLatestSubjects,
fetchTopicSubjects,
fetchTopicComments,
fetchComments,
fetchUsers,
rateSubject, // rate subject
rateUser,
rateComment,
trueVote,
falseVote,
})(Subject, Comment);
因为您要返回单个 subject
作为缩减程序操作的一部分,您可能想用更新后的 subject
交换现有的 subject
,因此您需要更新你的减速机是:
case TRUE_VOTE:
const index = state.items.subjects.findIndex( subject => action.subjects.id === subject.id );
return {
items: [...state.items.slice(0, index), action.subjects, ...state.items.slice( index + 1 )] };
};
为了更清楚一点,您当然也可以更改您的操作,以表明您返回的只是一个主题
// true vote
export const trueVote = (_id, currTrue) => (dispatch) => {
console.log("trueVote hitting");
fetch(`/api/subjects/subject/true/${_id}/${currTrue}`, {
method: "PUT",
})
.then((res) => res.json())
.then((subject) =>
dispatch({
type: TRUE_VOTE,
subject
})
);
};
在你的 reducer 中你会更清楚你只需要 1 个主题