setState被回调执行阻止

时间:2019-07-09 06:59:54

标签: reactjs

我正在从事React项目,并制作了自己的手风琴组件。在应用程序的一页上,我需要呈现一个手风琴列表,并且单击打开标题后,必须从API中获取每个手风琴的内容。当前,我的手风琴有一个状态叫做open,我允许组件的用户传递两个回调:onClickOpen和onClickClose。这是一个handleClick函数,用于设置状态,然后在setState回调中调用该回调。

我的问题是,似乎从未调用过set state,因为在我控制台日志中this.state.open的值始终为false。我假设回调发生了什么,但我不确定是什么。

手风琴组件(称为Section):

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';

import {
  SectionContainer,
  ToggleButton,
  HeaderContainer,
  Heading,
  BodyContainer,
  Body,
  Icon,
  Button,
} from './sectionStyles';


class Section extends Component {

  static propTypes = {
    titles: PropTypes.array.isRequired,
    children: PropTypes.node,
    data: PropTypes.object,
    noExpand: PropTypes.bool,
    showSecondButton: PropTypes.bool,
    onSecondButtonClick: PropTypes.func,
    color: PropTypes.string,
    widths: PropTypes.array,
    fontSize: PropTypes.number,
    fontWeight: PropTypes.number,
    secondIconName: PropTypes.string,
    secondIconColor: PropTypes.string,
    onClickOpen: PropTypes.func,
    onClickClose: PropTypes.func,
  };

  static defaultProps = {
    children: null,
    noExpand: false,
    showSecondButton: false,
    onSecondButtonClick: () => {},
    data: {},
    onClickOpen: () => {},
    onClickClose: () => {},
  };

  state = {
    open: false,
  };

  handleClick = () => {
    if (this.props.noExpand) return;

    if (this.state.open) {
      this.setState({ open: false }, () => {
        if (this.props.onClickClose) this.props.onClickClose();
      });
    } else {
      this.setState({ open: true }, () => {
        if (this.props.onClickOpen) this.props.onClickOpen();
      });
    }
  }

  renderHeadings() {
    return this.props.titles.map((title, i) => {
      return (
        <Heading width={this.props.widths ? this.props.widths[i] : null} fontSize={this.props.fontSize} fontWeight={this.props.fontWeight} key={shortid.generate()}>
          {
            this.props.showSecondButton &&
            (
              <Button onClick={() => this.props.onSecondButtonClick(this.props.data)}>
                <Icon className="material-icons md-32" color={this.props.secondIconColor}>{this.props.secondIconName}</Icon>
              </Button>
            )
          }
          {title}
        </Heading>
      );
    });
  }

  render() {
    return (
      <SectionContainer>
        { !this.props.noExpand && <ToggleButton color={this.props.color} open={this.state.open} />}
        <HeaderContainer open={this.state.open} onClick={() => this.handleClick()}>
          {this.renderHeadings()}
        </HeaderContainer>
        <BodyContainer open={this.state.open}>
          <Body>
            {this.props.children}
          </Body>
        </BodyContainer>
      </SectionContainer>
    );
  }
}

export default Section;

样式化组件的样式:

import styled from 'styled-components';

import { colors } from '../../../theme/vars';

import { themes as themeTypes } from '../../../types';

export const SectionContainer = styled.div(props => ({
  boxSizing: 'border-box',
  position: 'relative',
  width: '100%',
  borderBottom: `1px solid ${colors.SLATE_BLUE_20}`,
  borderLeft: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
  borderRight: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
}));

export const ToggleButton = styled.button`
  box-sizing: border-box;
  position: absolute;
  right: 0;
  margin: 0;
  padding: 0;
  height: 3em;
  width: 3em;
  outline: 0;
  border: 0;
  background: none;
  text-indent: -9999%;
  pointer-events: none;
  font-size: 14px;
  line-height: 18px;
  &:before {
      content: '';
      display: block;
      position: absolute;
      height: 12px;
      width: 4px;
      border-radius: .3em;
      background: ${props => props.color || colors.MOSS_GREEN_100};
      transform-origin: 50%;
      top: 50%;
      left: 50%;
      transition: all .25s ease-in-out;
      transform: ${props => props.open === true ? `translate(0%, -50%) rotate(-45deg)` : `translate(75%, -50%) rotate(45deg)` };
    }
  &:after {
      content: '';
      display: block;
      position: absolute;
      height: 12px;
      width: 4px;
      border-radius: .3em;
      background: ${props => props.color || colors.MOSS_GREEN_100};
      transform-origin: 50%;
      top: 50%;
      left: 50%;
      transition: all .25s ease-in-out;
      transform: ${props => props.open === true ? `translate(0%, -50%) rotate(45deg)` : `translate(-75%, -50%) rotate(-45deg)`};
    }
`;

export const HeaderContainer = styled.div`
  box-sizing: border-box;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-direction: row;
  flex-wrap: nowrap;
  padding-left: 16px;
  height: 40px;
  width: 100%;
  overflow: hidden;
  cursor: pointer;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: ${colors.SLATE_BLUE_100};
  background: ${props => props.open === true ? colors.MOSS_GREEN_FOCUS_13 : colors.WHITE};
  border-bottom: 1px solid ${colors.SLATE_BLUE_20};
`;

export const Heading = styled.div(props => ({
  boxSizing: 'border-box',
  display: 'flex',
  flexDirection: 'row',
  justifyContent: 'space-between',
  verticalAlign: 'center',
  width: props.width || '25%',
  fontSize: `${props.fontSize || 14}px`,
  fontWeight: props.fontWeight || 400,
  lineHeight: '32px',
  color: colors.SLATE_BLUE_100,
}));


export const BodyContainer = styled.div(props => ({
  boxSizing: 'border-box',
  overflow: 'hidden',
  height: props.open === true ? 'auto' : '0px',  // how to transition this...
  transition: 'all .2s ease-in',
  borderLeft: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
  borderRight: props.open === true ? `1px solid ${colors.MOSS_GREEN_FOCUS_13}` : 'none',
  borderBottom: props.open === true ? `2px solid ${colors.MOSS_GREEN_100}` : 'none',
  background: colors.WHITE,
}));

export const Body = styled.div(props => ({
  boxSizing: 'border-box',
  padding: '1em',
  color: '#333',
  lineHeight: '1.3',
}));

export const Icon = styled.i`
  color: ${props => props.color};
  font-size: '32px';
  margin-top: auto;
`;

export const Button = styled.button`
  display: flex;
  flex-direction: row;
  align-self: center;
  margin-right: 10px;
  background: none;
  border: none;
`;

我像这样使用Section组件:

class MEPlotAccordion extends Component {

  static propTypes = {
    row: PropTypes.object.isRequired,
    clearSelectedNode: PropTypes.func.isRequired,
    fetchSelectedNode: PropTypes.func.isRequired,
    selectedNode: PropTypes.object,
    isFetchingSelectedNode: PropTypes.bool.isRequired,
  };

  static defaultProps = {
    selectedNode: {},
  };

  onClickOpen = (nodeId) => {
  // the callback that is called from the Section handleClick
    this.props.fetchSelectedNode({ type: nodeTypes.MODEL_ELEMENT, id: nodeId });
  };

  onClickClose = () => {
    this.props.clearSelectedNode();
  };

  renderMetaDataPage() {
    if (this.props.isFetchingSelectedNode) {
      return 'Loading...';
    }
    if (this.props.selectedNode) {
      // this component requires data from API to render
      return (
        <ModelElementMetaDataPage
          modelElement={this.props.selectedNode}
        />
      );
    }
    return null;
  }


  renderSeries() {
    return this.props.row.series.map((series) => {
      return (
        <Section
          key={series.id}
          titles={[`${series.name} - ${series.tagName}`]}
          onClickOpen={() => this.onClickOpen(series.id)}
          onClickClose={() => this.onClickClose()}
          fontSize={18}
          fontWeight={500}
        >
          {this.renderMetaDataPage()}
        </Section>
      );
    });
  }

  render() {
    return (
      <AccordionContainer>
        {this.renderSeries()}
      </AccordionContainer>
    );
  }
}

const mapStateToProps = state => ({
  state,
  selectedNode: selectors.selectedNode(state),
  isFetchingSelectedNode: selectors.isFetchingSelectedNode(state),
});

const mapDispatchToProps = dispatch => ({
  clearSelectedNode: () => dispatch(actions.clearSelectedNode()),
  fetchSelectedNode: (nodeType, id) => dispatch(actions.fetchSelectedNode(nodeType, id)),
});

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

1 个答案:

答案 0 :(得分:1)

因此,在与问题作者讨论之后,问题在于父组件之一正在每个渲染器上卸载。卸载的原因是,为组件赋予了shortid.generate()在每个渲染器上生成的不同密钥。