Search code examples
javascriptreactjsecmascript-6setstatereact-table

Implementing a global search filter across react-table : React+ react-table


I am trying to implement a global search filter that searches for the key across the entire table. I am attaching a change handler and on every input I trigger a callback that searches that key across that data and the value is being set.It is getting filtered as I type-in the characters but I want the search to work when I enter multiple serach values like string1,string2 in the search input

Code Sandbox: https://codesandbox.io/s/jolly-bhabha-iqcx1

Code

import React from "react";
import ReactDOM from "react-dom";
import { Input } from "semantic-ui-react";
import ReactTable from "react-table";
import "react-table/react-table.css";
import "./styles.css";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [
        { firstName: "aaaaa", status: "Pending", visits: 155 },
        { firstName: "aabFaa", status: "Pending", visits: 155 },
        { firstName: "adaAAaaa", status: "Approved", visits: 1785 },
        { firstName: "aAaaaa", status: "Approved", visits: 175 },
        { firstName: "adaSaaa", status: "Cancelled", visits: 165 },
        { firstName: "aasaaa", status: "Cancelled", visits: 157 },
        { firstName: "aweaaaaaewea", status: "Approved", visits: 153 },
        { firstName: "aaaaaa", status: "Submitted", visits: 155 }
      ],
      columns: [],
      searchInput: ""
    };
  }

  componentDidMount() {
    let columns = [
      {
        Header: "First Name",
        accessor: "firstName",
        sortable: false,
        show: true,
        displayValue: " First Name"
      },
      {
        Header: "Status",
        accessor: "status",
        sortable: false,
        show: true,
        displayValue: "Status "
      },
      {
        Header: "Visits",
        accessor: "visits",
        sortable: false,
        show: true,
        displayValue: " Visits "
      }
    ];
    this.setState({ columns });
  }

  handleChange = event => {
    this.setState({ searchInput: event.target.value }, () => {
      this.globalSearch();
    });
  };

  globalSearch = () => {
    let { data, searchInput } = this.state;
    if (searchInput) {
      let filteredData = data.filter(value => {
        return (
          value.firstName.toLowerCase().includes(searchInput.toLowerCase()) ||
          value.status.toLowerCase().includes(searchInput.toLowerCase()) ||
          value.visits
            .toString()
            .toLowerCase()
            .includes(searchInput.toLowerCase())
        );
      });
      this.setState({ data: filteredData });
    }
  };

  render() {
    let { data, columns, searchInput } = this.state;
    return (
      <div>
        <br />
        <Input
          size="large"
          name="searchInput"
          value={searchInput || ""}
          onChange={this.handleChange}
          label="Search"
        />
        <br />
        <br />
        <ReactTable
          data={data}
          columns={columns}
          defaultPageSize={10}
          className="-striped -highlight"
        />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


Solution

  • This is not a problem with react-table. In the current implementation,

    1. for the very first time, you're filtering from original data.

    2. Second time when you try to filter, you're trying to filter results from previously filtered data(the current data in state object is filtered results of previous attempt).

    I suggest to try(this is forked from yours): https://codesandbox.io/s/eloquent-clarke-w1ehv

    Maintain your original data as immutable source, and filter from it every time your search input is changed rather than filtering from current data object in the state. Take a look at the fork I've attached.

    If your data is coming from ajax call, you should probably maintain two state variables:

    this.state = {
       data: [],
       filteredData: [],
       columns: [],
       searchInput: ""
    };
    
    componentDidMount() {
      yourapicall.then(data => {
        this.setState({
          data // whatever data you get.
        });
      });
    }
    
    // And in your global search
    
    globalSearch = () => {
        let { searchInput, data } = this.state;
        let filteredData = data.filter(value => {
        return (
            value.firstName.toLowerCase().includes(searchInput.toLowerCase()) ||
            value.status.toLowerCase().includes(searchInput.toLowerCase()) ||
            value.visits
              .toString()
              .toLowerCase()
              .includes(searchInput.toLowerCase())
          );
        });
        this.setState({ filteredData });
    };
    

    And conditionally render your react-table

    <ReactTable
       data={filteredData && filteredData.length ? filteredData : data}
       columns={columns}
       defaultPageSize={10}
       className="-striped -highlight"
    />
    

    Hope this is helpful!