状态更改而无需调用setState

时间:2019-04-18 15:00:15

标签: javascript reactjs

所以我试图在React中构建一个Notifications组件。组件的状态包含一组通知,这些通知是对象。他们的关键之一是“看到”。您可能会猜到,看到的目的主要是视觉。每次用户单击通知时,我都会运行一个功能,该功能应该设置为在数据库(为了保持一致性)和在本地状态(对于UI)中看到的通知。

数据库部分工作得很好,但是由于某种原因,状态更改不起作用。当我放置一些console.log时,很奇怪的是,我什至在调用this.setState之前,将'seen'属性更改为1。我已经呆了几个小时了,我不知道发生了什么。

现在,一些代码:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import classes from './Notifications.module.css';
import * as actions from '../../../store/actions';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import moment from 'moment';
import {Redirect} from 'react-router-dom';

class Notifications extends Component {
  constructor(props) {
    super(props);

    // Set an interval to update notifications every 4 minutes.
    this.update = setInterval(() => {
      this.props.fetchNotifications(this.props.username)
    }, 240000)
  }

  state = {
    opened: false,
    count: 0,
    notifications: []
  }

  componentDidUpdate(prevProps, prevState) {
    if (!prevProps.username && this.props.username) this.props.fetchNotifications(this.props.username);
    if (!prevProps.notifications && this.props.notifications) {
      this.setState({notifications: this.props.notifications, count: this.props.notifications.filter(not => !not.seen).length});
    }

    if (this.props.notifications) {
      if (JSON.stringify(this.state.notifications) !== JSON.stringify(prevState.notifications)) {
        this.setState({count: this.state.notifications.filter(not => !not.seen).length})
      }
    }
  }

  componentWillUnmount() {
    // Clear the update interval
    clearInterval(this.update);
  }

  redirect(model, model_id) {
    switch (model) {
      case 'sealant_customer':
        return <Redirect to={`/profile/sealant-customer/${model_id}`} />;
      case 'unapproved_business':
        return <Redirect to={`/profile/unapproved-business/${model_id}`} />;
      case 'business':
        return <Redirect to={`/profile/business/${model_id}`} />;
      case 'missed_call':
        return <Redirect to={`/data/missed-calls`} />;
      default: return null;
    }
  }

  render() {
    let content = (
      <React.Fragment>
        <div className={classes.svgWrapper}>
          <p className={classes.counter} style={{opacity: this.state.count === 0 ? '0' : '1'}}>{this.state.count}</p>
          <FontAwesomeIcon icon='bell' onClick={() => this.setState(prevState => ({opened: !prevState.opened}))} />
        </div>

        {this.state.opened && <div className={classes.notificationsWrapper}>
          <ul className={classes.notificationsList}>
            {this.state.notifications.length !== 0 ? Array.from(this.state.notifications).map(notifi => {
              let icon;
              switch (notifi.model) {
                case 'sealant_customer':
                case 'person':
                  icon = 'user';
                  break;
                case 'business':
                case 'unapproved_business':
                  icon = 'warehouse';
                  break;
                default: icon = 'user';
              }
              let classArray = [classes.notification];
              if (!notifi.seen) classArray.push(classes.unseen);

              return (
                <li key={notifi.id} className={classArray.join(' ')} onClick={ () => {
                  // If already seen, simply redirect on click.
                  if (notifi.seen) return this.redirect(notifi.model, notifi.model_id);

                  let newNotifications = [...this.state.notifications];

                  // If not seen, mark as seen in State & in Database.
                  let index = newNotifications.findIndex(not => notifi.id === not.id);
                  newNotifications[index].seen = 1;
                  this.setState({ notifications: newNotifications});
                  this.props.markSeen(notifi.id, this.props.username);

                  // Redirect.
                  return this.redirect(notifi.model, notifi.model_id);
                }}>
                  <div className={classes.iconWrapper}>
                    <FontAwesomeIcon icon={icon} />
                  </div>

                  <div className={classes.textWrapper}>
                    <p className={classes.msg}>
                      {notifi.message}
                    </p>

                    <label className={classes.ago}>
                      {moment(notifi.date).fromNow()}
                    </label>
                  </div>
                </li>
              )
            }) : <li className={classes.notification} style={{cursor: 'default'}}><p style={{whiteSpace: 'nowrap'}}>No notifications to show...</p></li>}
          </ul>
        </div>}
      </React.Fragment>
    )
    return (
      <div className={classes.wrapper}>
        {content}
      </div>
    )
  }
}

const mapStateToProps = state => {
  return {
    username: state.auth.username,
    notifications: state.data.notifications
  };
};

const mapDispatchToProps = dispatch => {
  return {
    fetchNotifications: username => dispatch(actions.fetchNotifications(username)),
    markSeen: (id, username) => dispatch(actions.markSeen(id, username))
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Notifications);

任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:0)

对于未来的读者-解决方案:

问题在于,当我呼叫let newNotifications = [...this.state.notifications];行时,确实确实创建了一个通知副本,但是却在其中保留了原始notification对象。

一旦我将行newNotifications[index].seen = 1更改为newNotifications[index] = {...newNotifications[index], seen: 1},一切都会像魅力一样。