简体   繁体   中英

How to display a snackbar conditionally based on redux state using thunks?

I'm writing a website in react which displays information on mobile apps and I'm using redux to store the information about the current app. I have an input text field located in the header where the user can type for some app id and if it's a valid id they will be redirected to another page, if the id is not valid a snackbar will be displayed with appropriate message and if the user just hit enter a snackbar will also be displayed with appropriate message. AppNotFound is the component which wraps snackbar.

I'm using redux thunks to dispatch an action which checks whether the app id is valid (it's an async function) inside onKeyDown method ( getAppInfo ). Ideally I'd like to get the result from the redux already in onKeyDown method. But because the action is dispatched asynchronically I can't.

So I thought to let render display a snackbar based on the value of found property (whether app was found or not). So at first found would be undefined because the async dispatch wouldn't have returned the result in the render but then found would become true or false and then we can display the snackbar. The render would automatically be called the second time because the props have changed. But this doesn't happen.

What is the correct way in terms of patterns to achieve what I want? I don't want to use componentWillReceiveProps as it's deprecated.

This is my code:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '@material-ui/core/InputBase';
import { withStyles } from '@material-ui/core/styles';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAppInfo } from '../../actions/appActions.js';
import constants from '../../constants.js';

import { AppSearchBarInputStyles } from '../styles/Material-UI/muiStyles.js';
import AppNotFound from './AppNotFound.js';

import * as log from 'loglevel';
log.setLevel("debug")


class AppSearchBarInput extends Component {
  state = {
    appId: '',
    snackBarOpen: false
  }

  onChange = e => {
    this.setState({ appId: e.target.value });
  }

  onKeyDown = e => {
    const { appId } = this.state;
    const { found } = this.props.app;

    if (e.keyCode === constants.ENTER_KEY) {

      this.props.getAppInfo({ appId });
      if (found) {
        this.props.history.push('/moreInfo');
      } else {
        this.setState({
          snackBarOpen: true
        });
      }

      this.setState({
        appId: ''
      });
    }
  }

  handleCloseSnackBar = () => {
    this.setState({
      snackBarOpen: false
    });
  }

  render() {

    const { classes, app } = this.props;
    const { snackBarOpen } = this.state;
    const { found, appId } = app;
    let message = '';

    if (!found) {
      message = appId === '' ? constants.MESSAGES.APP_BLANK() : constants.MESSAGES.APP_NOT_FOUND(appId);
    }

    let displayWhenFoundIsUndefined = null;

    if (found === undefined) {
      displayWhenFoundIsUndefined = <div>Loading...</div>;
    } else {
      displayWhenFoundIsUndefined = <AppNotFound message={message}
                   open={!found}
                   onClose={this.handleCloseSnackBar}/>;
    }
    return (
      <div>
        <InputBase
          placeholder="Search…"
          classes={{
            root: classes.inputRoot,
            input: classes.inputInput,
          }}
          onChange={this.onChange}
          onKeyDown={this.onKeyDown}
          value={this.state.appId} />
        {displayWhenFoundIsUndefined}
      </div>
    )
  }
}

AppSearchBarInput.propTypes = {
  classes: PropTypes.object.isRequired
}

const mapStateToProps = state => ({
  app: state.app.app
});

const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesConnected = connect(mapStateToProps, { getAppInfo })(AppSearchBarWithStyles);
export default withRouter(AppSearchBarWithStylesConnected);

Since no one still didn't answer this question, I will offer the solution which I recently arrived to.

The problem essentially is that the display of AppSearchBarInput depends mainly on whether the app was found or not. This action must be asynchronic because the information is from Web API. Therefore, I was using redux-thunk s and was receiving mobile app information in props. However the snackbarOpen property was in the state which is a problem because the snackbar depends on state property which in itself depends on props which are received asynchronically.

The solution to the predicament is to move snackbarOpen to props as well. So now an action to set snackbarOpen to true should be dispatched directly from getAppInfo thunk if the app was not found, as well as from onKeyDown when the app is blank. On the other hand an action to set snackbarOpen to false should be dispatched from handleCloseSnackBar .

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