React – Material App Bar Custom Search Filter

Overview

Using the React Material UI library, create an App Bar with a search field that show results as you type. The search bar is available at Material UI Website.

Source files for this example here.

To handle state management, a class component is created as a parent for the NavBar. This will pass and filter the list down to the Navigation and Filtered List function components.

Parent Layout Component

This component will import two JSON data files and then filter them with the search string. Filtering is done with startsWith and indexOf to check if the string is contained in the name or title.

Source code for MainNavParent.js

import React, { Component } from 'react';
import MainNav from './MainNav';
import listColors from './data/colors.json';
import listUsers from './data/users.json';
export default class MainNavParent extends Component {
  
  constructor(props) {
    super(props); 
    this.state = {
        tableArray: [],        
        searchUsersList: listUsers,
        searchFilteredUsersList: [],
        searchColorsList: listColors,
        searchFilteredColorsList: [],        
        open: true,
        showResults: false,        
        searchInput: '',
    };     
  }
  
  handleSearchInput = (event) => {       
    var searchStr = event.target.value;
    var newUsersList = [];
    var newColorsList = [];

    var count = 0; // Use a counter to limit the number of results returned
    
    // Search both starts with and indexOf(Contains the search string)
    this.state.searchUsersList.map((item, i) => {      
      if((item.title.toLowerCase().indexOf(searchStr) > 0 || item.title.toLowerCase().startsWith(searchStr)) && count < 3) {  
        newUsersList.push(item);
        count++;
      }                    
    });    

    count = 0; // Reset the counter
    this.state.searchColorsList.map((item, i) => {      
      if((item.name.toLowerCase().indexOf(searchStr) > 0 || item.name.toLowerCase().startsWith(searchStr)) && count < 3) {  
          newColorsList.push(item);
          count++;
      }                    
    });    

    if(searchStr.length > 0) {
      this.setState({showResults: true});
    }
    else {
      this.setState({showResults: false});
    }
    this.setState({searchFilteredUsersList: newUsersList});
    this.setState({searchFilteredColorsList: newColorsList});
    this.setState({searchInput: event.target.value});
  }  
  
  handleSearchFilter = () => {
    this.setState({showResults: true})
  }
  
  handleCloseSearchFilter = () => {
    this.setState({showResults: false})    
  }

  handleClearSearchField = (event) => {
    this.setState({searchInput: ''});
    this.setState({showResults: false});
  }
    
  render() {
    return (
      <React.Fragment>
        <MainNav searchUsersList={this.state.searchFilteredUsersList} 
                 searchColorsList={this.state.searchFilteredColorsList}
                 handleClearSearchField={this.handleClearSearchField}
                 handleSearchInput={this.handleSearchInput}  
                 searchInput={this.state.searchInput}
                 showResults={this.state.showResults}/>  
      </React.Fragment>
    );
  }
}

Navigation Component

The MainNav function component is derived from the Search & App Bar in the Material UI library,

Source for MainNav.js

<div className={classes.search}>
    <div className={classes.searchIcon}>
    <SearchIcon />
    </div>
    <InputBase
      placeholder="Search…"
      value={props.searchInput}
      classes={{
          root: classes.inputRoot,
          input: classes.inputInput,
      }}
      inputProps={{ 'aria-label': 'search' }}
      onChange={props.handleSearchInput}    
    />
</div>        

Body Section


<main
  className={clsx(classes.content, {
    [classes.contentShift]: open,
  })}
>
  
  <div className="container">
    <SearchBarFilter show={Boolean(props.showResults)} 
                      list={props.searchList}
                      searchInput={searchInput}                           
                      handleCloseSearchFilter={handleCloseSearchFilter}/>
    
    <MainRouter />
  </div>
</main>

Filter List Component

This component is a combination of a CSS generated 3d shadow box that can grow and shrink with a Material List. The list is generated and filtered from the parent class component and passed down to the MainNav.js and then the FilteredList.js,

Source for FilteredList.js

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import ImageIcon from '@material-ui/icons/Image';

const useStyles = makeStyles((theme) => ({
  root: {
    width: '100%',
    maxWidth: 360,
    backgroundColor: theme.palette.background.paper,
  },
}));


export default function FilteredList(props) {
  const classes = useStyles();
  const numbers = [1, 2, 3, 4, 5];

  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
        {number}
    </li>
  );
  return (
    <React.Fragment>
    
    <List className={classes.root}>
    {props.list.map((item) => (
    
        <ListItem button>
         <ListItemAvatar>
            <Avatar>
                <ImageIcon />
            </Avatar>
            </ListItemAvatar>
            <ListItemText primary={item} secondary="Jan 9, 2014" />
         </ListItem>
     ))}
     </List>
    </React.Fragment>
  );
}

CSS Styling

Create a 3d box with CSS that can grow and shrink with the Material List.

CSS for FilteredList.css

.box {
    display: block;
    position: fixed;
    top: 14px;
    right: -17px;
    max-width: 50vw;
    min-width: 20vw;
    max-height: 70vh;
    min-height: 10vh;
    margin: 40px;    
    background-color: white; /* Needed for IEs */
    border: none;
    padding: 10px;
    opacity: 1;
    z-index: 99;
    -moz-box-shadow: 5px 10px 8px #888888;
    -webkit-box-shadow: 5px 10px 8px #888888;
    box-shadow: 5px 10px 8px #888888;      
    zoom: 1;
}

Source files for this example here.