状态未更新

时间:2018-11-29 23:37:40

标签: javascript reactjs immutability

我目前正在React的图像上传器组件上工作。一切正常,但是删除方法。我已经阅读了几篇有关如何更新数组/对象以及不可变状态的思想的文章。这是我尝试过的:

  1. .filter()
  2. .slice()
  3. .splice()(我怀疑这样做是否可以修改原始数组)

无论尝试什么,我总是会收到此错误:

Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

这是我的代码:

ImageUploader.js

import React, { Component } from 'react';

import styled from 'styled-components';

import FileUploadButton from '../FileUploadButton';
import ImagePreviewer from './ImagePreviewer';

import { 
  Typography, 
  Button 
} from '@material-ui/core';
import theme from '../../../theme';

import uuidv5 from 'uuid/v5';

const StyledPreviewerContainer = styled.div`
  display: flex;
  margin: ${theme.spacing.unit}px 0;
  overflow: hidden;
  overflow-x: auto;
`;

export default class ImageUploader extends Component {
  state = {
    uploadedImages: []
  }

  updateImages = e => {
    const { uploadedImages } = this.state,
    files = [...e.target.files],
    inexistentImages = files.filter(image => uploadedImages.indexOf(image) === -1);

    this.setState(prevState => ({
      uploadedImages: [...prevState.uploadedImages, ...inexistentImages]
    }));

    this.props.onChange(e);
  }

  removeImages = image => {
    const { uploadedImages } = this.state,
    imageIndex = uploadedImages.indexOf(image);

    this.setState(prevState => ({
      uploadedImages: prevState.uploadedImages.filter((image, index) => index !== imageIndex)
    }));
  };

  render() {
    const {
      className,
      label,
      id, 
      multiple, 
      name, 
      onBlur
    } = this.props, {
      uploadedImages
    } = this.state;

    return (
      <div className={className}>
        <Typography>
          {label}
        </Typography>
        <StyledPreviewerContainer>
          {uploadedImages.map(image =>
            <ImagePreviewer 
              src={URL.createObjectURL(image)}
              image={image} 
              removeImages={this.removeImages}
              key={uuidv5(image.name, uuidv5.URL)}
            />
          )}
        </StyledPreviewerContainer>
        <FileUploadButton 
          id={id}
          multiple={multiple}
          name={name}
          onChange={this.updateImages}
          onBlur={onBlur}
        />
        <Button>
          Delete all
        </Button>
      </div>
    );
  }
}

ImagePreviewer.js

import React, { Component } from 'react';

import styled from 'styled-components';

import AnimatedImageActions from './AnimatedImageActions';

import { ClickAwayListener } from '@material-ui/core';
import theme from '../../../theme';

const StyledImagePreviewer = styled.div`
  height: 128px;
  position: relative;
  user-select: none;
  cursor: pointer;

  &:not(:last-child) {
    margin-right: ${theme.spacing.unit * 2}px;
  }
`;

const StyledImage = styled.img`
  height: 100%;
`;

export default class ImagePreviewer extends Component { 
  state = {
    actionsOpened: false
  };

  openActions = () => {
    this.setState({
      actionsOpened: true
    });
  };

  closeActions = () => {
    this.setState({
      actionsOpened: false
    });
  };

  render() {
    const {
      actionsOpened
    } = this.state,
    {
      src,
      image,
      removeImages
    } = this.props;

    return (
      <ClickAwayListener onClickAway={this.closeActions}>
        <StyledImagePreviewer onClick={this.openActions}>
          <StyledImage src={src} />
          <AnimatedImageActions 
            actionsOpened={actionsOpened} 
            image={image}
            removeImages={removeImages}
          />
        </StyledImagePreviewer>
      </ClickAwayListener>
    );
  }
}

AnimatedImageActions.js

import React from 'react';

import styled from 'styled-components';

import { Button } from '@material-ui/core';
import { Delete as DeleteIcon } from '@material-ui/icons';
import { fade } from '@material-ui/core/styles/colorManipulator';
import theme from '../../../theme';

import { 
  Motion, 
  spring
} from 'react-motion';

const StyledImageActions = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  color: ${theme.palette.common.white};
  background-color: ${fade(theme.palette.common.black, 0.4)};
  width: 100%;
  height: 100%;
  display: flex;
`;

const StyledImageActionsInner = styled.div`
  margin: auto;
`;

const StyledDeleteIcon = styled(DeleteIcon)`
  margin-right: ${theme.spacing.unit}px;
`;

const AnimatedImageActions = ({ actionsOpened, removeImages, image }) => 
  <Motion
    defaultStyle={{
      scale: 0
    }}
    style={{
      scale: spring(actionsOpened ? 1 : 0, {
        stiffness: 250
      })
    }}
  >
    {({ scale }) =>
      <StyledImageActions style={{
        transform: `scale(${scale})`
      }}>
        <StyledImageActionsInner>
          <Button 
            color="inherit"
            onClick={removeImages(image)}  
          >
            <StyledDeleteIcon />
            Delete
          </Button>
        </StyledImageActionsInner>
      </StyledImageActions>
    }
  </Motion>
;

export default AnimatedImageActions

任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:3)

onClick={removeImages(image)}应该是onClick={()=>removeImages(image)}吗?

否则,removeImagessetState的渲染过程中调用AnimatedImageActions