javascriptreactjsreact-hooksuse-statersuite

How to filter a table using multiple user inputs on a React.js app


I have 2 main components on a react app: one is a table with a bunch of data (using Rsuite Table), the other, above the table, is just a div with multiple input text fields. As simple as this:

input-fields

If, for example, on the 'firstName' input field, the user just types a T letter, the table grid should immediately display only names that include a T in the name. This would be fine, my actual problem, is how to make this happen for the 3 inputs you see in the image above. This is, if the user types in stuff on multiple input text boxes? Take a look below:

Here's how my main App.js looks:

function App() {
  const [dummyData, setDummyData] = useState([]);
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [city, setCity] = useState('');

  const firstNameHandler = (input) => {
    setFirstName(input);
  }
  const lasttNameHandler = (input) => {
    setLastName(input);
  }
  const firstNameHandler = (input) => {
    setCity(input);
  }

  useEffect(() => {
    const generateRandomData = () => {
      setDummyData([...Array(100).keys().map(key => {
        return {
          firstName: faker.name.firstName(),
          lastName: faker.name.lastName(),
          city: faker.address.city()
        }
      })
    )}  
    generateRandomData();
  }, []);

  return(
    <Filters changeFirstName={firstNameHandler} changeLastName={lastNameHandler} changeCity={cityHandler}/>
    <InfoTable items={dummyData}/>
);
  
}
export default App;

PS: The states are being lifted up from the Filterscomponent. I use useEffect to generate random data when the component first loads. And the dummyData, passed into InfoTable is just an array with objects randomly generated using faker.js and they look like this:

{
 firstName: "John",
 lastName: "Smith",
 city: "San Francisco"
}

My Filters.jscomponent looks like this:

function Filters = (props) => {
  const firstNameInputHandler = (event) => {
    props.changeFirstName(event.target.value);
  }
  const lastNameInputHandler = (event) => {
    props.changeLastName(event.target.value);
  }
  const cityInputHandler = (event) => {
    props.changeCity(event.target.value);
  }
  
  return(
    <div className="user_inputs">
      <label htmlFor="firstName">First Name:</label>
      <input id="firstName" type="text" onChange={firstNameInputHandler}/>
    </div>
    <div className="user_inputs">
      <label htmlFor="lastName">Last Name:</label>
      <input id="lastName" type="text" onChange={lastNameInputHandler}/>
    </div>
    <div className="user_inputs">
      <label htmlFor="city">City:</label>
      <input id="city" type="text" onChange={cityInputHandler}/>
    </div>
  );
}
export default Filters;

So here as you can see, I'm lifting the states up, so I have them avaliable on the App.js so I could filter the array that I pass on the table props.

My InfoTable.jscomponent is very straight-foward and looks like this, again, using Rsuite Table:

function InfoTable = (props) => {
  return(
    <Table virtualized height={500} data={props.items} rowHeight={30}>
       <Column width={60}>
         <HeaderCell>First Name</HeaderCell>
         <Cell dataKey="firstName"/>
       </Column>
       <Column width={60}>
         <HeaderCell>Last Name</HeaderCell>
         <Cell dataKey="lastName"/>
       </Column>
       <Column width={60}>
         <HeaderCell>City</HeaderCell>
         <Cell dataKey="city"/>
       </Column>
    </Table>
  );
}
export default InfoTable;

The table displays indeed fine, but what I want is the data to be filtered according to the various text-fields that may or may not be typed in by the user. For example: if the user types Jo on first name input field, and S on the last name input field, I want the table to display only objects that match this criteria. And then if the user types California, I want the table to re-adjust to only people from california with first names that include Jo and last names that include S. And if the user deletes everything, the table should be with the original 100 values.

One way I figured a small part of the issue is, on App.js I check if firstName state length is greater than 0, if so, I run the .filter on the array I pass as props. So inside the return, regarding the grid component, I render it conditionally as suck:

return(
  bla bla bla (...)
  {firstName.length === 0 && <InfoTable items={dummyData} />
  {firstName.length > 0 && <InfoTable items={dummyData.filter((obj) => {
    return obj.firstName.includes(firstName)})
  }
);

I can 'kind of' use this approach to check if there is input or not in a field, and render the table with the 100 rows or filtered by that criteria. The problem is, how do I make this work for the 3 inputs? For example: if the user types Jo on First Name input field, and S on the Last Name input field, I want the table to display only rows that match this criteria (objects with first names that include Jo AND last names that include S). And then if the user types California on the City field, I want the table to re-adjust to only people from California with first names that include Jo and last names that include S. And if the user deletes the text from all 3 inputs, the table should re-ajust to display the original 100 values.

Please help.


Solution

  • With the example given in mind, the solution is as clean and simple as:

    const filterCriteria = (obj) => {
      return obj.firstName.includes(firstName)
      && obj.lastName.includes(lastName)
      && obj.city.includes(city);
    }
    
    return(
      <InfoTable items={dummyData.filter(filterCriteria)} />
    );
    

    This provides the desired result.