Node.js Redux状态未在history.push上重置
我有两个React组件,Node.js Redux状态未在history.push上重置,node.js,mongodb,reactjs,mongoose,redux,Node.js,Mongodb,Reactjs,Mongoose,Redux,我有两个React组件,MyPolls和NewPoll MyPolls呈现某个用户创建的最后4个轮询,每次按下“加载更多”按钮时,它会呈现4个以上的轮询。这是通过对我的mongoDB进行mongoose调用来完成的,方法是按日期按降序排序,并使用skip和limit 我现在的问题是,每当我创建一个新的民意测验时,它都会通过我的操作创建者中的history.push(“/MyPolls”)将我重定向到MyPolls组件,并且我的民意测验列表的顺序会混乱 假设我的mongoDB目前有8个投票: 12
MyPolls
和NewPoll
MyPolls
呈现某个用户创建的最后4个轮询,每次按下“加载更多”按钮时,它会呈现4个以上的轮询。这是通过对我的mongoDB进行mongoose调用来完成的,方法是按日期按降序排序,并使用skip
和limit
我现在的问题是,每当我创建一个新的民意测验时,它都会通过我的操作创建者中的history.push(“/MyPolls”)
将我重定向到MyPolls
组件,并且我的民意测验列表的顺序会混乱
假设我的mongoDB目前有8个投票:
12345678
(1是最老的,8是最新的)
当我查看MyPolls
时,它将显示8 7 6 5
。如果单击“加载更多”,您将看到其他4:8 7 6 5 4 3 2 1
但是在您创建一个新的轮询之后,9
,您将被重定向到MyPolls
,它将显示此顺序8 7 6 5 9 8 7 6
(在初始渲染时显示8而不是4)
这是什么原因造成的?好像我的减速器状态没有复位
MyPolls.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Loading from '../Loading';
import Poll from './Poll';
class MyPolls extends Component {
constructor(props) {
super(props);
this.state = {
skip: 0,
isLoading: true,
isLoadingMore: false,
};
}
componentDidMount() {
this.props.fetchMyPolls(this.state.skip)
.then(() => {
setTimeout(() => {
this.setState({
skip: this.state.skip + 4,
isLoading: false
});
}, 1000);
});
}
loadMore(skip) {
this.setState({ isLoadingMore: true });
setTimeout(() => {
this.props.fetchMyPolls(skip)
.then(() => {
const nextSkip = this.state.skip + 4;
this.setState({
skip: nextSkip,
isLoadingMore: false
});
});
}, 1000);
}
renderPolls() {
return this.props.polls.map(poll => {
return (
<Poll
key={poll._id}
title={poll.title}
options={poll.options}
/>
)
})
}
render() {
console.log(this.props.polls);
console.log('skip:', this.state.skip);
return (
<div className='center-align container'>
<h2>My Polls</h2>
{this.state.isLoading ? <Loading size='big' /> :
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
alignItems: 'center',
alignContent: 'center'
}}>
{this.renderPolls()}
</div>}
<div className='row'>
{this.state.isLoadingMore ? <Loading size='small' /> :
<button
className='btn red lighten-2 wave-effect waves-light' onClick={() => this.loadMore(this.state.skip)}>
Load More
</button>}
</div>
</div>
);
}
}
function mapStateToProps({ polls }) {
return { polls }
}
export default connect(mapStateToProps, actions)(MyPolls);
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field, FieldArray, arrayPush } from 'redux-form';
import * as actions from '../../actions';
import { withRouter } from 'react-router-dom';
const cardStyle = {
width: '500px',
height: '75px',
margin: '10px auto',
display: 'flex',
alignItems: 'center',
padding: '10px'
};
class NewPoll extends Component {
constructor(props) {
super(props);
this.state = {
showOptions: false,
option: '',
title: ''
};
this.onOptionInputChange = this.onOptionInputChange.bind(this);
this.onAddOption = this.onAddOption.bind(this);
this.renderOption = this.renderOption.bind(this);
this.renderOptionCard = this.renderOptionCard.bind(this);
this.renderTitle = this.renderTitle.bind(this);
this.renderTitleCard = this.renderTitleCard.bind(this);
}
onOptionInputChange(event) {
this.setState({ option: event.target.value });
}
onAddOption() {
const { dispatch } = this.props;
dispatch(arrayPush('newPollForm', 'options', this.state.option));
this.setState({ option: '' });
}
renderOption(props) {
return (
<ul>
{props.fields.map((option, index) => (
<li key={index}>
<div
className='card'
style={cardStyle}>
<Field
type='text'
name={option}
index={index}
component={this.renderOptionCard}
/>
<i
className='material-icons right'
onClick={() => props.fields.remove(index)}
>
delete
</i>
</div>
<div className='red-text'>
{props.meta.error }
</div>
</li>
))}
</ul>
);
}
renderOptionCard({ index, input }) {
return (
<span className='card-title'
style={{ flex: '1' }}>
{`${index + 1})`} {input.value}
</span>
);
}
renderTitle({ input, type, placeholder, meta: { touched, error }}) {
return (
<div>
<div className='input-field inline'>
<input {...input}
type={type}
placeholder={placeholder}
style={{ width: '350px' }}
/>
<div className='red-text'>
{touched && error}
</div>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={() => {
this.setState({ title: input.value });
input.value = '';
}}
disabled={!input.value}>
Add Title
<i className='material-icons right'>
add
</i>
</button>
</div>
)
}
renderTitleCard({ input }) {
return (
<div
className='card'
style={cardStyle}>
<span className='card-title' style={{ flex: '1' }}>
<strong><u>{input.value}</u></strong>
</span>
<i className='material-icons right' onClick={() => this.setState({ title: '' })}>
delete
</i>
</div>
)
}
onPollSubmit(values) {
const { history } = this.props;
this.props.submitPoll(values, history);
}
render() {
return (
<div className='center-align'>
<h3>Create a new poll:</h3>
<form onSubmit={this.props.handleSubmit(this.onPollSubmit.bind(this))}>
<Field
type='text'
placeholder='Title'
name='title'
component={this.state.title ? this.renderTitleCard : this.renderTitle}
/>
<FieldArray
name='options' component={this.renderOption}
/>
<div className='row'>
<div className='inline input-field'>
<input
value={this.state.option} onChange={this.onOptionInputChange}
placeholder='Option'
style={{ width: '300px' }}
/>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={this.onAddOption}
disabled={!this.state.option}
>
Add Option
<i className='material-icons right'>
add
</i>
</button>
</div>
<button
type='submit'
className='teal btn-large waves-effect waves-light'
>
Submit
<i className='material-icons right'>
send
</i>
</button>
</form>
</div>
);
}
}
function validate(values) {
const errors = {};
if (!values.title) {
errors.title = 'You must provide a title';
}
if (!values.options || values.options.length < 2) {
errors.options = { _error: 'You must provide at least 2 options' };
}
return errors;
}
NewPoll = reduxForm({
form: 'newPollForm',
validate
})(NewPoll);
export default connect(null, actions)(withRouter(NewPoll));
投票路线:
export const submitPoll = (values, history) => async dispatch => {
const res = await axios.post('/api/polls', values);
history.push('/mypolls');
dispatch({ type: FETCH_USER, payload: res.data });
}
export const fetchMyPolls = (skip) => async dispatch => {
const res = await axios.get(`/api/mypolls/${skip}`);
dispatch({ type: FETCH_MY_POLLS, payload: res.data });
}
app.post('/api/polls', requireLogin, (req, res) => {
const { title, options } = req.body;
const poll = new Poll({
title,
options: options.map(option => ({ option: option.trim() })),
dateCreated: Date.now(),
_user: req.user.id
});
poll.save();
res.send(req.user);
});
app.get('/api/mypolls/:skip', requireLogin, (req, res) => {
Poll.find({ _user: req.user.id })
.sort({ dateCreated: -1 })
.skip(parseInt(req.params.skip))
.limit(4)
.then(polls => {
res.send(polls);
});
});
import { FETCH_MY_POLLS, UPDATE_POLL } from '../actions/types';
export default function(state = [], action) {
switch(action.type) {
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
case UPDATE_POLL:
return (
[...state].map(poll => {
if (poll._id === action.payload._id) {
return action.payload;
}
return poll;
})
)
default:
return state;
}
}
投票缩减器:
export const submitPoll = (values, history) => async dispatch => {
const res = await axios.post('/api/polls', values);
history.push('/mypolls');
dispatch({ type: FETCH_USER, payload: res.data });
}
export const fetchMyPolls = (skip) => async dispatch => {
const res = await axios.get(`/api/mypolls/${skip}`);
dispatch({ type: FETCH_MY_POLLS, payload: res.data });
}
app.post('/api/polls', requireLogin, (req, res) => {
const { title, options } = req.body;
const poll = new Poll({
title,
options: options.map(option => ({ option: option.trim() })),
dateCreated: Date.now(),
_user: req.user.id
});
poll.save();
res.send(req.user);
});
app.get('/api/mypolls/:skip', requireLogin, (req, res) => {
Poll.find({ _user: req.user.id })
.sort({ dateCreated: -1 })
.skip(parseInt(req.params.skip))
.limit(4)
.then(polls => {
res.send(polls);
});
});
import { FETCH_MY_POLLS, UPDATE_POLL } from '../actions/types';
export default function(state = [], action) {
switch(action.type) {
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
case UPDATE_POLL:
return (
[...state].map(poll => {
if (poll._id === action.payload._id) {
return action.payload;
}
return poll;
})
)
default:
return state;
}
}
应用程序演示:
(使用riverfish@gmail.com
和密码123
登录)
Github:组件正在调用
componentDidMount
上的0
的fetchMyPolls
。发生的情况是,首先您访问的是/mypolls
,服务器返回polls[8,7,6,5]
。这使您的投票状态[8,7,6,5]
。当您创建一个新的轮询(比如9)时,您将被重定向到/mypolls/
,并使用0
再次调用fetchMyPolls
。请注意,在pollsReducer
中
case FETCH\u MY\u轮询:
返回[…状态,…操作.有效负载];
它只是在州末追加新的民意调查。这就是为什么新状态变成[8,7,6,5,9,8,7,6]
你说得对,减速器不会重置状态。您的应用程序中没有描述执行此操作的操作。事实上,不重置状态是一件好事,因为客户端已经收到了信息,为什么不使用它们而不是向后端发出新的请求呢
一个好的解决方案是定义一个新的操作,例如FETCH\u new\u POLL
并向pollsReducer
case FETCH\u NEW\u POLL
返回[…action.payload,…state];
您还需要修改reducer,使其在您的状态中只有唯一的项
此外,在App.js
中,只有在状态中没有轮询时,才可以使用oneter
获取前4个轮询。这样,您可以从MyPolls
的componentDidMount
中删除fetchMyPolls
调用