Yos
Yos

Reputation: 1376

How to combine Material-UI's snackbar and input components in react?

I'm using Material-UI components to build my website. I have a header component with a search field which uses mui InputBase under the hood. When user enters empty input (that is they don't enter anything and just click enter) I want to display a mui Snackbar which will warn the user the no meaningful input was entered.

I can't get the combination of the two components to work together. In addition because search field state doesn't really change when user enters nothing it doesn't reload so if user repeatedly presses Enter the snackbar won't appear. I use this.forceUpdate(); but is there a more elegant way to implement such logic?

This is my code:

for the search input field:

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';

class AppSearchBarInput extends Component {
  state = {
    appId: ''
  }

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

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

    if (e.keyCode === constants.ENTER_KEY && appId !== '') {
      this.props.getAppInfo({ appId });
      this.setState({
        appId: ''
      });
    }
    this.props.history.push('/app/appInfo');
    this.forceUpdate();
  }

  render() {
    const { classes } = this.props;
    const { appId } = this.state;
    console.log(`appId from AppSearchInput=${appId === ''}`);

    return (
      <div>
        <InputBase
          placeholder="Search…"
          classes={{
            root: classes.inputRoot,
            input: classes.inputInput,
          }}
          onChange={this.onChange}
          onKeyDown={this.onKeyDown}
          value={this.state.appId} />
        { appId === '' ? <AppNotFound message={constants.MESSAGES.APP_BLANK()}/> : ''}
      </div>
    )
  }
}

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

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

for the snackbar:

import React from 'react';
import Snackbar from '@material-ui/core/Snackbar';
import constants from '../../constants.js';
import SnackbarMessage from './SnackbarMessage.js';


class AppNotFound extends React.Component {
  state = {
    open: true,
  };

  handleClose = event => {
    this.setState({ open: false });
  };

  render() {
    const { message } = this.props;
    return (
      <Snackbar
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'center',
        }}
        open={this.state.open}
        autoHideDuration={6000}
        onClose={this.handleClose}
      >
        <SnackbarMessage
          onClose={this.handleClose}
          variant="warning"
          message={message}
        />
      </Snackbar>
    );
  }
}

export default AppNotFound;

Upvotes: 0

Views: 3031

Answers (1)

Roman Batsenko
Roman Batsenko

Reputation: 81

I think the good way to achieve what You want is by adding another state property called snackBarOpen which will help You to determine if user has entered empty value or something meaningful:

AppSearchBarInput Component

state = {
  appId: '',
  snackBarOpen: false
}

handleKeyDown = (e) => {
  if (e.keyCode === 13 && e.target.value === '') {
    this.setState({
      appId: e.target.value,
      snackBarOpen: true
    });
  } else {
    this.setState({
      appId: e.target.value
    })
  }
}

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

And then just render also <AppNotFound /> in render() method(it will be hidden by default because it will depend on open prop):

render() {
  const { snackBarOpen } = this.state;

  return(
    <div>
      /* <InputBase /> here */
      <AppNotFound message={/* Your message here */} open={snackBarOpen} onClose={this.handleCloseSnackBar} />
    </div>
  )      
}

AppNotFound Component

You can remove all methods now and leave only render() one which will look next:

render() {
  const { message, open, onClose } = this.props;
  return (
    <Snackbar
      // ...
      open={open}
      // ...
      onClose={onClose}
    >
      <SnackbarMessage
        onClose={onClose}
        // ...
        message={message}
      />
    </Snackbar>
  );
}

Hope that my answer will be useful :)

Upvotes: 1

Related Questions