Use Buttons to trigger filter function on react-table in React

17,187

Solution 1

For React-Table filter to be controlled externally by buttons, you should take a look at the Controlled Table example. Then the table component becomes a controlled component.

There, you can set the state of the table filter externally and use both of the props:

<ReactTable  ...(your other props)
        filtered={this.state.filtered}
        onFilteredChange={filtered => this.setState({ filtered })}
 />

The filtered prop will monitor change in the state. So whenever you update its value through your letter buttons via e.g.

this.setState({filtered: { id: "dName", value: "A"}})

the table's filter will get updated. Also, onFilteredChange should work the other direction, namely the embedded filtering of the react-table can update the local state. So that you can monitor it and use its value within your DataGrid component.

One other option could be to avoid using local states and implement it in redux, though. Because states hanging around components are eventually becoming source of errors and increasing complexity of debugging.

UPDATE -- As per the question owner's comment for more details:

In order to use FilterButtons and DataGrid together, you can define a container component that encapsulates both FilterButtons and DataGrid. The container component keeps the shared state, and the filtering function operates on the state function. But data will still reside within the datagrid.

class DataGridWithFilter extends React.Component {

// you need constructor() for binding alphaFilter to this. 
// otherwise it cannot call this.setState
constructor(props) {
   super(props);
   this.alphaFilter = this.alphaFilter.bind(this);
}

// personally i do not use ids for passing data. 
// therefore introduced the l parameter for the letter
alphaFilter(e,l) {
  console.log(e.target.id);
  this.setState({filtered: { id: "dName", value: l}});
}

 render(){
   return <div>
      <DataGrid filtered={this.state.filtered} 
                filterFunc={this.alphaFilter}
      </DataGrid>
      <BottomMenu filtered={this.state.filtered}
                  filterFunc={this.alphaFilter} />
   </div>
 }
}

Also this above thing requires the use of prop filterFunc from within BottomMenu and FilterButtons components.

class FilterButtons extends React.Component {
render() {
 const {props} =  this;
 return (
    <div>
       <button onClick={(e) => props.filterFunc(e, "A")} id="A" className="letter">A</button>
       <button onClick={(e) => props.filterFunc(e, "B")} id="B" className="letter">B</button>
       <button onClick={(e) => props.filterFunc(e, "C")} id="C" className="letter">C</button>
    </div>
  );
 }
}

const BottomMenu = props => (
  <div className="btm-menu">
  <div className="toprow">
  <div className="filter-keys">
    <FilterButtons filterFunc = {props.filterFunc} />
  </div>
</div>
</div>
); 



class DataGrid extends React.Component {
  constructor() {
    super();
    this.state = {
      data: [],
    };
  }

  componentWillMount() {

    fetch('http://localhost:3000/rooms.json').then((results) => results.json()).then((data) => {
        console.log(data.room);

        this.setState({
          data: data.room
        })
      })
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        <ReactTable
          data={data}
          filterable
          defaultFilterMethod={(filter, row) =>
            String(row[filter.id]) === filter.value}
          columns={[
                {
                  Header: "Name",
                  accessor: "dName",
                  filterMethod: (filter, row) =>
                    row[filter.id].startsWith(filter.value)
                },
                {
                  Header: "Department",
                  accessor: "dDept"
                },
                {
                  Header: "Room",
                  accessor: "dRoom"
                },
                {
                  Header: "Map",
                  accessor: "dRoom",
                  id: "over",
                }

              ]
            }

          defaultPageSize={14}
          className="-striped -highlight"
          filtered = {this.props.filtered}
          onFilteredChange = {filtered => this.props.filterFunc({filtered})}
        />
        <br />
       </div>
    );
  }
}

I have not checked against typo etc but this should work.

Solution 2

In React, you can update components when state changes. The only way to trigger is to use this.setState()

So I would change my state object something like this:

state = {
  date: [],
  filter: ""
};

so here is the new file:

// In Directory.js
class FilterButtons extends React.Component {

alphaFilter = (word) => {
  this.setState({
    filter: word
  });
};

render() {

 return (
    <div>
       <button onClick={() => this.alphaFilter("A")} id="A" className="letter">A</button>
       <button onClick={() => this.alphaFilter("B")} id="B" className="letter">B</button>
       <button onClick={() => this.alphaFilter("C")} id="C" className="letter">C</button>
    </div>
  );
 }
}

const BottomMenu = props => (
  <div className="btm-menu">
  <div className="toprow">
  <div className="filter-keys">
    <FilterButtons />
  </div>
</div>
</div>
);

right after you call alphaFilter() your component will update. And when you put your filter function in it it's going display as expected. So I would choose the .map() function of array. You can either use filter() or return after comparing data in map()

Share:
17,187
donlaur
Author by

donlaur

Front-end and Back-End Developer

Updated on July 26, 2022

Comments

  • donlaur
    donlaur almost 2 years

    I don't know how to word this. I am learning React and I have data loaded into React-Table via fetch. I tried using React-Table and just custom plain divs and tables.

    I want to create a touch buttons of the alphabet from A, B, C, D ... Z. Those buttons should call the filter for the letter that is in the button. So, for example the buttons are the following.

    // In Directory.js
    class FilterButtons extends React.Component {
    
    alphaFilter(e) {
      console.log(e.target.id);
      // somehow filter the react table
    }
    
    render() {
    
     return (
        <div>
           <button onClick={this.alphaFilter} id="A" className="letter">A</button>
           <button onClick={this.alphaFilter} id="B" className="letter">B</button>
           <button onClick={this.alphaFilter} id="C" className="letter">C</button>
        </div>
      );
     }
    }
    
    const BottomMenu = props => (
      <div className="btm-menu">
      <div className="toprow">
      <div className="filter-keys">
        <FilterButtons />
      </div>
    </div>
    </div>
    );
    

    // I have a class Directory extends Component that has the BottomMenu in it

    // I also have a DataGrid.js with the React Table in there

    class DataGrid extends React.Component {
      constructor() {
        super();
        this.state = {
          data: [],
        };
      }
    
      componentWillMount() {
    
        fetch('http://localhost:3000/rooms.json').then((results) => results.json()).then((data) => {
            console.log(data.room);
    
            this.setState({
              data: data.room
            })
          })
      }
    
      render() {
        const { data } = this.state;
        return (
          <div>
            <ReactTable
              data={data}
              filterable
              defaultFilterMethod={(filter, row) =>
                String(row[filter.id]) === filter.value}
              columns={[
                    {
                      Header: "Name",
                      accessor: "dName",
                      filterMethod: (filter, row) =>
                        row[filter.id].startsWith(filter.value)
                    },
                    {
                      Header: "Department",
                      accessor: "dDept"
                    },
                    {
                      Header: "Room",
                      accessor: "dRoom"
                    },
                    {
                      Header: "Map",
                      accessor: "dRoom",
                      id: "over",
                    }
    
                  ]
                }
    
              defaultPageSize={14}
              className="-striped -highlight"
            />
            <br />
    
          </div>
        );
      }
    }
    
    export default DataGrid; 
    

    At this point I am unsure what to do to get the button click of one of the A, B, C letters to filter the React Table. I do not want the Input field option that is always used because I want only buttons as the user will not have a keyboard, only touch.

    Basically, React Table or just any table that can be filtered by clicking buttons with a letter that gets passed back to the filter. If I was using JQuery I would use a button click and then filter that way. I still haven't learned all the ins and outs of React and how to get this done. I also want to use external buttons to sort but that should be easier, hopefully.

    Thanks. Sorry if all of this doesn't make sense, I am just trying to lay it out. Again, no keyboard, only touch on a touch screen so the input field isn't going to work for me.

  • donlaur
    donlaur over 6 years
    I changed the code and word does show the button is clicked. How do I update the filterable part or filterMethod in the render of my DataGrid.js file as it is still attached to the input field and not using the buttons? Thanks, somewhat lost, pretty lost here.
  • donlaur
    donlaur over 6 years
    Thanks. I am looking into it and I will report back.
  • donlaur
    donlaur over 6 years
    Thanks. I tried to implement this and onclick the filtered did not seem to update the react-table. I might try to get his in a codepen or something. Caan you explain the redux option? How that would work? Thanks.
  • mcku
    mcku over 6 years
    Yes I am happy to explain redux but it should be topic of another question I have reviewed your code a bit more, you should not make any async calls in componentWillMount because it may leave your table uninitialized, because the state change may go undetected. Have you looked at the React Table controlled component example? It has a codepen that you can play with, too.
  • mcku
    mcku over 6 years
    @donlaur I have checked the code on codepen. Here you can find a working example. Enjoy. Please don't forget to accept it as an answer. codesandbox.io/s/wy574pkrl8
  • donlaur
    donlaur over 6 years
    When I try to move over the codepen example when I click the A,B,C buttons I get the error of TypeError: props.filterFunc is not a function.
  • mcku
    mcku over 6 years
    @donlaur I can't reproduce that on my browser. Don't forget this line in the render function of the button const {props} = this;. Or use this.props.filterFunc instead.
  • donlaur
    donlaur over 6 years
    is there a definition of filterFunc anywhere? The error I get is that it is not a function.
  • mcku
    mcku over 6 years
    Yep, the <DataGrid filterFunc={this.alphaFilter} ... /> defines it. It should be there in the sandbox, as a method within DataGridWithFilter component.
  • donlaur
    donlaur over 6 years
    While this works with the 2 sample rows, how to make it work with the imported JSON file that was read in the componentDidMount?
  • mcku
    mcku over 6 years
    @donlaur you can put the fetch request in the componentDidMount method of the DataGridWithFilter component, and remove the sample lines in its constructor. Please see the commented part in the componentDidMount.