Node.js Redux状态未在history.push上重置

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

我有两个React组件,
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
调用