简体   繁体   中英

Alternative to component using props for state

I have a bottom level component that is used to display success/ error messaging in my application. Currently it is using props for it's state, which has worked up until this point. The problem I'm facing is when returned in a certain component the state isn't changing once the component is rendered once. IE The message will show up once correctly and then it doesn't show up again because the state stays set to false.

Here is the function that renders the component. This function sits in the parent component.

renderSnackBar(type, message) {

    console.log('snackbar function invoked');

    return <Snackbar open={true} type={type} message={message} />
  }

Here is the child component

class MySnackbar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
    open: this.props.open,
  };
}

  handleClose = (event, reason) => {
    if (reason === 'clickaway') {
      return;
    }

    this.setState({ open: false });
  };

  render() {

    const { type, message } = this.props

    let icon = <SuccessIcon color={colors.lightBlue} />
    let color = colors.lightBlue
    console.log('state', this.state, 'props', this.props);

    switch (type) {
      case 'Error':
        icon = <ErrorIcon color={colors.red} />
        color = colors.red
        break;
      case 'Success':
        icon = <SuccessIcon color={colors.lightBlue} />
        color = colors.lightBlue
        break;
      case 'Warning':
        icon = <WarningIcon color={colors.orange} />
        color = colors.orange
        break;
      case 'Info':
        icon = <InfoIcon color={colors.lightBlue} />
        color = colors.lightBlue
        break;
      default:
        icon = <SuccessIcon color={colors.lightBlue} />
        color = colors.lightBlue
        break;
    }

    return (
      <Snackbar
        anchorOrigin={{
          vertical: 'bottom',
          horizontal: 'left',
        }}
        open={this.state.open}
        autoHideDuration={6000}
        onClose={this.handleClose}
        message={
          <div style={{ padding: '12px 24px', borderLeft: '5px solid '+color, borderRadius: '4px'}}>
            {icon}
            <div style={{ display: 'inline-block', verticalAlign: 'middle', maxWidth: '400px' }}>
              <Typography variant='body1' style={{ fontFamily: 'Montserrat-SemiBold', fontSize: '12px' }}>{type}</Typography>
              <Typography variant='body1' style={{ fontFamily: 'Montserrat-Medium', fontSize: '10px', color: colors.darkGray }} noWrap>{message}</Typography>
            </div>
          </div>
        }
        action={[
          <IconButton
            key="close"
            aria-label="Close"
            color={colors.darkGray}
            onClick={this.handleClose}
          >
            <CloseIcon />
          </IconButton>,
        ]}
      />
    );
  }
}

export default MySnackbar;

From what I gather using props to initialize state is an antipattern. How can I use this component without using props as state?

Basically, the problem you have right now is caused by the fact that you have 2 sources of truth (1 from props, and 1 from state) for determining whether your SnackBar should open or not.

So, what we want to do is to make it so that there is only 1 source of truth for your component. Usually, we want to keep the props as the source of truth, not the state .

What we could do is to add a prop called onClose to your SnackBar, and have the SnackBar invoke the function inside handleClose . onClose should change the open value from the parent component, so the value passed down to SnackBar is updated accordingly. Now, the SnackBar does not have to have its own state anymore, it can just rely on the props passed to it. Here is an example implementation on codesandbox .

Whenever you receive the open prop that is not the same as state you can change state with getDerivedStateFromProps

static getDerivedStateFromProps({open}, state) {
  if (open !==state.open) {
    //use open from props
    return {
      ...state,
      open
    };
  }  
  // Return null to indicate no change to state.
  return null;
}

You may now have a component that doesn't close anymore depending on how you call renderSnackbar, if that's the case then I need to know more on how you call the renderSnackbar function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM