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

Viewed 2030

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.

2 Answers

Sorry that I don‘t have enough reputations to comment, so I answer the question

If this is a demo, or data size is small, I think filter is totally ok, just simply chain the filter:

const newData = originalData.filter(condition1).filter(condition2).filter(condition3)

If this is a productive web application, maybe we should let the API from backend does the stuff about filter, because data maybe huge and may rely on some kind of search engines

If you really want to do it only on the client side, and data maybe large too, there should be some kind of technques like search engines only for client side, but I don‘t have experiences about this part

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.

Related