Building chainable filter components in React? - arrays

What's the most "React"y way to build reusable, chainable filter components?
Let's say I have an input array:
[
{name: 'Generic t-shirt', size: 'xxl', available: 35},
{name: 'Generic t-shirt', size: 'md', available: 2},
{name: 'Generic t-shirt', size: 'sm', available: 5},
{name: 'Really awesome shirt', size: 'md', available: 0}
]
And a keyword search for the name, a dropdown for size, and a "sold out" boolean checkbox for availability.
Right now, I have the filtering code inside the rendering loop:
const [productsFilteredByFullText, setProductsFilteredByFullText] = useState;
const [productsFilteredBySize, setProductsFilteredBySize] = useState;
const [productsFilteredByAvailability, setProductsFilteredByAvailability] = useState;
const searchResults = useMemo(() => {
let filteredProducts = eventsFromAPI; // array of products as input
filteredProducts = filteredEvents.filter(product => fullTextFilter(product));
filteredProducts = filteredEvents.filter(product => sizeFilter(product));
filteredProducts = filteredEvents.filter(product => availabilityFilter(product));
return filteredProducts;
}, [productsFilteredByFullText, productsFilteredBySize, productsFilteredByAvailability]);
And the UI inside JSX:
<div>
// Fulltext search
<input
type="text"
placeholder="Keyword search"
value={searchTerm}
onChange={fullTextHandler}
/>
// Size dropdown
<Dropdown
options={allAvailableSizes}
onChange={sizeHandler}
/>
// Sold out checkbox
<input
name="soldout"
type="checkbox"
checked={productsFilteredByAvailability}
onChange={availabilityHandler}
/>
<h1>Results</h1>
{filteredProducts.map(item => (
<Product item={item} />
))}
</div>
This works, but is not very reusable at all. Let's say I have another product category, scarves, that are all one size, but I still want to be able to re-use the filter component and logic for name and availability.
Is there a way to modularize/componentize BOTH the filter logic AND the presentation JSX into separate filter components, and be able to chain them together arbitrarily? Something like this pseuocode:
<TShirtList>
<NameFilter/>
<SizeFilter/>
<AvailabilityFilter/>
</TShirtList>
<ScarfList>
<NameFilter/>
<AvailabilityFilter/>
</ScarfList>
<ServicesList>
<NameFilter/>
</ServicesList>
So that each filter is its own component, able to be inserted into any array of products anywhere? Like how can a React component also provide its own logic/functions that other components can use (product array in, filtered product array + JSX UI out, but chainable).
Is that even the right way to think about this problem...? I'm confused about how to best build this architecturally.

Answer to this question
What's the most "React"y way to build reusable, chainable filter
components?
I suggest separate display & data logic by using hooks. Move all data related logic into a hook, and import that hook to a react component.
// useProducts.js
const useProducts = () => {
const [products, setProducts = useState([]);
const [filters, setFilters] = useState({}); // eg. filters = { size: 'large', availability: 'instock' }
// Get all products on initial load
useEffect(() => {
fetch(....).then(response => setProducts(response.data));
}, []);
// function to set filter.
const setFilter = (name, value) => {
setFilters({ ...filters, [name]: value });
}
// Loop filters and filter product
const filteredProducts = [...products];
for (const [key, value] of Object.entries(filters)) {
// Get list of products from somewhere.
filteredProducts = filterProducts.filter(p => p[key] === value);
}
return [filteredProducts, { setFilter }];
}
// Product List
import React from 'react';
import useProducts from './useProducts';
const ProductList = (props) => {
const [products, { setFilter }] = useProducts();
return (
<div>
<div className="toolbar">
<NameFilter onChange={value => setFilter('name', value)} />
<SizeFilter onChange={value => setFilter('size', value)} />
<AvailabilityFilter onChange={value => setFilter('availability', value)} />
</div>
<div>
{products.map(p => <ProductItem {...p} />}
</div>
</div>
);
}
Additionally, you can even modularize it more by import { setFilter } in the filter component itself. And you can remove the 'onChange' from the ProductList.
const NameFilter = () => {
const [, { setFilter }] = useProducts();
return (
<input
type="text"
onChange={e => setFilter('name', e.target.value)}
/>
);
}
// And now, we can remove onChange from each filter in Product List component
<div>
<div className="toolbar">
<NameFilter />
<SizeFilter />
<AvailabilityFilter />
</div>
<div>
{products.map(p => <ProductItem {...p} />}
</div>
</div>
*Note: Above code is just psuedo, it just shows the idea.

Any issues with conditionally filtering?
const searchResults =(text, size) => {
let filteredProducts = eventsFromAPI; // array of products as input
filteredProducts = text ? filteredEvents.filter(product => fullTextFilter(text)) : filteredProducts;
filteredProducts = size ? filteredEvents.filter(product => sizeFilter(product)) : filteredProducts
filteredProducts = filteredEvents.filter(product => availabilityFilter(product));
return filteredProducts;
};
size can be boolean.
can also be ... (psuedo code)
const Search =({filter = []}) => {
let filteredProducts = eventsFromAPI; // array of products as input
filteredProducts = filter.indexOf('something') > -1 ? filteredEvents.filter(product => fullTextFilter(text)) : filteredProducts;
filteredProducts = filter.indexOf('size') > -1 ? filteredEvents.filter(product => sizeFilter(product)) : filteredProducts
filteredProducts = filteredEvents.filter(product => availabilityFilter(product));
return filteredProducts;
};
then u can call search({ filter: ['size', 'availability']}) or if you made it in to an component then <Search filter={['size', 'availability']} products={products} />
or even
const Search = ({ size = null, text = '', availability = true}) => {
... conditionally filtering
return //whatever product u render
}

Related

How can i add and remove components dinamically?

I want to add and remove the components dynamically, so far just i can add, but when i tried to remove it remove too weird, lets say i dont want to remove just i would like to hide the components
import {
MinusCircleOutlined,
PlusOutlined,
} from '#ant-design/icons'
import { useState } from "react"
const MyInput = ({ index, removeInput }) => {
return (<div >
<Input placeholder="Email address" />
<MinusCircleOutlined className="icon-left" onClick={() => { removeInput(index) }} />
</div>
)
}
const MyComponent = ({ }) => {
const [form] = Form.useForm()
const [index, setIndex] = useState(0)
const [inputsFields, setInputsFields] = useState([])
const [hiddenFields, setHiddenFields] = useState([])
const AddInput = () => {
const newInviteField = <MyInput index={index} removeInput={removeInput} />
setInputsFields([...inputsFields, newInviteField])
const newIndex = index + 1
setIndex(newIndex)
}
const removeInput = (currentIndex) => {
let a = hiddenFields
a.push(currentIndex)
setHiddenFields([...a])
}
return (
<Card>
<Form form={form} layout="vertical">
<Form.Item className='form-item item-container'>
{inputsFields.map((item, index) => !hiddenFields.includes(index) && <div key={index}>{item}</div>)}
</Form.Item>
<Form.Item >
<a href="#" onClick={AddInput}>Add</a>
</Form.Item>
</Form>
</Card>)
}
i tried to filter by the index, just showing the indexes does not into the hidden array !hiddenFields.includes(index)
the problem is when i am deleting, sometimes it is not deleting, sometimes other component is deleting
You should never use an array method index as key, if you modify the array. It has to be unique. When you delete element with index 2, the index 3 becomes index 2. This should not happend. You should not change the key prop value
The solution:
keep information about the inputs in the state, not the inputs itself.
// keep necessarry information for each input here.
// Like id, name, hidden, maybe what to render. Whatever you want
const [inputsFields, setInputsFields] = useState([{
id: 'name',
hidden: false
}])
// and map them
inputsFields.map(element => !element.hidden && <Input key={element.id} />)
When each element has unique id, you will delete the element with that id, not with the array map index
If you do not need that much info. Just make array of numbers in that state,
const counter = useRef(1)
const [inputsFields, setInputsFields] = []
const AddInput = () => {
counter.current += 1
setInputsFields(oldInputs => [...oldInputs, counter.current])
}
// and render them:
inputsFields.map(element => <Input key={element} />)

How to properly use state hook with useSelector?

I have two react-select elements in my app and the 2nd one should be populated with options based on the selection of the first. It works but when the Select #1 is cleared, #2 is not being reset.
So I decided to implement useState to help me update/reset the options for Select #2 but it's acting wonky. Am I missing something or is there a better way to accomplish this?
export const dataViewPage = ({
getData,
loadLocationsByType,
classes,
}) => {
const [stateValue, setStateValue] = useState('');
const [store, setStore] = useState('');
const [storeOptions, setStoreOptions] = useState([]); // Constant to populate options for Select #2
const locationType = 'store';
const Input = styled('input')({ display: 'none' });
/* Returns locations in selected state in array of objects with location_id and name */
const locations = useSelector((state) =>
getFilteredLocations(state, stateValue, locationType)
);
useEffect(() => { // Fetches locations data on state change
stateValue &&
loadLocationsByType({ locationType: locationType, state: stateValue });
}, [stateValue, loadLocationsByType]);
useEffect(() => { // Sets store options in expected format
if (locations) {
setStoreOptions(
convertObjArrayToSelectOptions(locations, 'location_id', 'name')
);
} else { // Reset if no state is selected?
setStoreOptions([]);
}
}, [locations, stateValue]);
}
/* Main input functions */
const handleStateChange = (e) => {
let stateEntered = e ? e.value : '';
setStateValue(stateEntered);
};
const handleStoreChange = (e) => {
let selectedStore = e ? e.value : '';
setStore(`T${selectedStore}`);
console.log(selectedStore);
};
return (
<div>
<div>
<p>Enter or select state</p>
<Select
classes={classes}
placeholder="Select state"
options={states}
onChange={handleStateChange}
isClearable
/>
</div>
<div>
<p>Select store:</p>
<Select
options={storeOptions}
isClearable
onChange={handleStoreChange}
/>
</div>
<label>
<Input type="submit" />
<Button
onClick={handleSubmit}
>
View data
</Button>
</label>
</div>
);
};

REACT-Send requests the selected dish options ids to my backend not working

How can I retrieve the dishId selected from my options react - select that shows me thedishType in order to send them my parent component FormRender and to the backend.
My first dropdown shows me: Menu1, Menu2...
My second one: type2...
So if I click ontype4, how can I store the related dishId(here = 4). I can click on several values i.e: type2 andtype3.
How do I keep the dish ids i.e : 2 and 3 and send them to my FormRender parent
Menus(first page of my multi - step form):
export default function Menus() {
const [selectionMenus, setSelectionMenus] = useState({});
const [selectionDishes, setSelectionDishes] = useState({});
const [menus, setMenus] = useState([])
const [date, setDate] = useState('')
useEffect(() => {
axios
.post(url)
.then((res) => {
console.log(res);
setMenus(res.data.menus);
})
.catch((err) => {
console.log(err);
});
}, []);
const names = menus?.map(item => {
return {
label: item.menuId,
value: item.name
}
})
const types = menus?.flatMap(item => {
return item.dishes.map(d => ({
label: d.dishId,
value: d.dishType
}))
})
const handle = (e) => {
if (e?.target?.id === undefined) return setInfo(e);
if (e?.target?.id === undefined) return setSelectionMenus(e);
if (e?.target?.id === undefined) return setSelectionDishes(e);
switch (e.target.id) {
case "date":
setDate(e.target.value);
break;
...
default:
}
}
};
return (
<>
<form>
<div>My menus</div>
<label>
Dishes :
<Dropdown
options={names}
value={selectionMenus}
setValue={setSelectionMenus}
isMulti={true}
/>
</label>
<label>
<Dropdown
options={types}
value={selectionDishes}
setValue={setSelectionDishes}
isMulti={true}
/>
</label>
<label>
Date:
<div>
<input
type="date"
name='date'
value={date}
onChange={handle}
id="date"
/>
</div>
</label>
...
</form>
<div>
<button onClick={() => nextPage({ selectionDishes, selectionMenus, date })}>Next</button>
</div>
</>
);
}
Here the parent Component FormRender that is supposed to retrieve the values of all dishId selected and send them to the backend:
export default function FormRender() {
const [currentStep, setCurrentStep] = useState(0);
const [info, setInfo] = useState();
const [user, setUser] = useState();
const headers = ["Menus", "Details", "Final"];
const steps = [
<Menus
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>,
<Details
backPage={() => setCurrentStep((s) => s - 1)}
nextPage={setUser}
/>,
<Final />
];
useEffect(() => {
if (info === undefined || user === undefined) return;
const data = {
date: info.date,
id: //list of dishId selected but don't know how to do that??
};
}, [info, user]);
return (
<div>
<div>
<Stepper steps={headers} currentStep={currentStep} />
<div >{steps[currentStep]}</div>
</div>
</div>
);
}
Dropdown:
export default function Dropdown({ value, setValue, style, options, styleSelect, isMulti = false }) {
function change(option) {
setValue(option.value);
}
return (
<div onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
value={value}
onChange={change}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
Here my json from my api:
{
"menus": [
{
"menuId": 1,
"name": "Menu1",
"Description": "Descritption1",
"dishes": [
{
"dishId": 2,
"dishType": "type2"
},
{
"dishId": 3,
"dishType": "type3"
},
{
"dishId": 4,
"dishType": "type4"
}
]
},
...
]
}
You already store the selected values inside the selectionMenus and selectionDishes states. So, if you want to send them to the parent FormRender component you can instead create those two states inside that component like this:
export default function FormRender() {
const [selectionMenus, setSelectionMenus] = useState();
const [selectionDishes, setSelectionDishes] = useState();
....
}
Then pass those values to the Menus component:
<Menus
selectionMenus={selectionMenus}
setSelectionMenus={setSelectionMenus}
selectionDishes={selectionDishes}
setSelectionDishes={setSelectionDishes}
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>
Subsequently, you will have to remove the state from the Menus component and use the one you receive from props:
export default function Menus({ selectionMenus, setSelectionMenus, selectionDishes, setSelectionDishes }) {
/*const [selectionMenus, setSelectionMenus] = useState({}); //remove this line
const [selectionDishes, setSelectionDishes] = useState({});*/ //remove this line
...
}
Finally, you can use inside your useEffect hook the two states and map them to only get the selected ids:
useEffect(() => {
// ... other logic you had
if(selectionDishes?.length && selectionMenus?.length){
const data = {
date: info.date,
id: selectionDishes.map(d => d.dishId),
idMenus: selectionMenus.map(m => m.menuId)
};
}
}, [info, user, selectionMenus, selectionDishes]);
react-select has options to format the component:
getOptionLabel: option => string => used to format the label or how to present the options in the UI,
getOptionValue: option => any => used to tell the component what's the actual value of each option, here you can return just the id
isOptionSelected: option => boolean => used to know what option is currently selected
onChange: option => void => do whatever you want after the input state has changed
value => any => if you customize the above functions you may want to handle manually the value
Hope it helps you

Cannot remove inputs array with filter

I am trying to remove an input field with filter function but it's not working.
In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update adding full component in question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rules.rule_0,
rule_1: teamData.rules.rule_1,
rule_2: teamData.rules.rule_2,
rule_3: teamData.rules.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(
teamData.rules.map((el) => ({
...el,
guid: uuidV4(),
}))
);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule_${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const removeInputs = (index) => {
const newList = inputs.filter((item, i) => index !== i); // <-- compare for matching index
setInputs(newList);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
// setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
// console.log("teamData.rules", teamData);
console.log("inputs", inputs);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.guid}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
When i do console.log(inputs) this is the data that I got:
0: 0: "t" 1: "e" 2: "s" guid: "e18595a5-e30b-4b71-8fc2-0ad9c0e140b2"
proto: Object 1: 0: "d" 1: "a" 2: "s" 3: "d" 4: "a" 5: "s" guid: "537ca359-511b-4bc6-9583-553ea6ebf544" ...
Issue
The issue here is that you are using the array index as the React key. When you mutate the underlying data and reorder or add/remove elements in the middle of the array then the elements shift around but the React key previously used doesn't move with the elements.
When you remove an element then all posterior elements shift forward and the index, as key, remains the same so React bails on rerendering the elements. The array will be one element shorter in length and so you'll see the last item removed instead of the one you actually removed.
Solution
Use a React key that is intrinsic to the elements being mapped, unique properties like guids, ids, name, etc... any property of the element that guarantees sufficient uniqueness among the dataset (i.e. the siblings).
const [inputs, setInputs] = useState(teamData.rules);
const removeInputs = (index) => {
// compare for matching index
setInputs(inputs => inputs.filter((item, i) => index !== i));
};
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.id}> // <-- use a unique property
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
If your teamData.rules initial state value doesn't have any unique properties to use then you can map this to a new array and add a sufficient id property.
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: generateId()***,
})));
*** this is a function you need to define yourself, or import from a module like uuid***
import { v4 as uuidV4 } from 'uuid';
...
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: uuidV4(),
})));
// Add more input
const addInputs = () => {
setInputs(inputs => [
...inputs,
{
name: `rule_${inputs.length + 1}`,
guid: uuidV4();
},
]);
};
Then when mapping use the guid property.
<div className="agreement-form-grid" key={data.guid}>
The issue is because you are trying to compare index with array item in filter method. You should use the second argument in filter which denotes the array index of the current iterating item
const removeInputs = (index) => {
const newList = inputs.filter((item,i) => index !== i);
setInputs(newList);
};
That's your solution, you are trying with item but you are comparing it with index that's wrong. You should do it like this,
const newList = inputs.filter((item, key) => index !== key);

Function Component always using previous state in render

I'm pretty new to using hooks and functional components.
I have a Filtered List. When I try to update the filter, it will use the last filter state instead of the new one. I must be missing some render/state change orders, but I can't seem to figure out what it is.
I appreciate any help I can get :)
Pseudo code below:
export default function TransferList(props) {
const [wholeList, setWholeList] = React.useState([]);
const [filteredList, setFilteredList] = React.useState([]);
const [filter, setFilter] = React.useState([]);
return (
<>
<TextField
value={filter}
onChange={(e) => {
// Set filter input
setFilter(e.target.value);
// Filter the list
let filtered = wholeList.filter(
(item) => item.indexOf(filter) !== -1
);
setFilteredList(filtered);
}}
/>
<List>
{filteredList.map((item) => (
<ListItem>Item: {item}</ListItem>
))}
</List>
</>
);
}
Inside onChange you should use save the value in a constant, filter will not update just after setFilter(filterValue) as this is an async operation.
<TextField
value={filter}
onChange={e => {
const filterValue = e.target.value;
// Set filter input
setFilter(filterValue);
// Filter the list
let filtered = wholeList.filter(item => item.indexOf(filterValue) !== -1);
setFilteredList(filtered);
}}
/>;
State updates are asynchronous and hence the filter state update doesn't reflect immediately afterwords
You must store the new filter values and set the states based on that
export default function TransferList(props) {
const [wholeList, setWholeList] = React.useState([]);
const [filteredList, setFilteredList] = React.useState([]);
const [filter, setFilter] = React.useState([]);
return (
<>
<TextField value={filter} onChange={(e) => {
// Set filter input
const newFilter = e.target.value;
setFilter(newFilter)
// Filter the list
let filtered = wholeList.filter(item => item.indexOf(newFilter) !== -1)
setFilteredList(filtered)
}} />
<List>
{filteredList.map(item => <ListItem>Item: {item}</ListItem>)}
</List>
</>
)
}
So it turns out I had to give the initial filtered list the entire unfiltered list first. For some reason that fixes it.
const [filteredList, setFilteredList] = React.useState(props.wholeList);
I initially wanted an empty filter to display nothing, but I may have to settle for showing the entire list when the filter is empty
Order your code to be increase readability
In clean code
Main changes:
Use destructuring instead of props
Out the jsx from the html return to increase readability
Use includes instead of indexOf
Add key to the list
export default function TransferList({ wholeList }) {
const [filteredList, setFilteredList] = React.useState(wholeList);
const [filter, setFilter] = React.useState([]);
const handleOnChange = ({ target }) => {
setFilter(target.value);
const updatedFilteredList = wholeList.filter(item => item.includes(target.value));
setFilteredList(updatedFilteredList);
}
const list = filteredList.map(item => {
return <ListItem key={item}>Item: {item}</ListItem>
});
return (
<>
<TextField value={filter} onChange={handleOnChange} />
<List>{list}</List>
</>
);
}
and I have a filter component the do the filter list inside.
import React, { useState } from 'react';
function FilterInput({ list = [], filterKeys = [], placeholder = 'Search',
onFilter }) {
const [filterValue, setFilterValue] = useState('');
const updateFilterValue = ev => {
setFilterValue(ev.target.value);
const value = ev.target.value.toLowerCase();
if (!value) onFilter(list);
else {
const filteredList = list.filter(item => filterKeys.some(key => item[key].toLowerCase().includes(value)));
onFilter(filteredList);
}
}
return (
<input className="filter-input" type="text" placeholder={placeholder}
value={filterValue} onChange={updateFilterValue} />
);
}
export default FilterInput;
the call from the father component look like this
<FilterInput list={countries} filterKeys={['name']} placeholder="Search Country"
onFilter={filterCountries} />
this is in my corona app.. you can look on my Github.
and see how I build the filter
(https://github.com/omergal99/hello-corona)

Resources