I am currently using Material-UI data tables and have a search routine that is similar to this Codesandbox example, which is just searching on the Name/Food column alone.
My current code is as follows:
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const handleSearch = e => {
let target = e.target;
setInput(target.value)
setFilterFn({
fn: items => {
if (target.value == "")
return items;
else
return items.filter(x => x.name.toLowerCase().includes(target.value))
}
})
}
And within my current search input, I am calling handleSearch as follows:
<Controls.Input
id="name"
label="Search Name"
value={input}
autoFocus={true}
className={classes.searchInput}
InputProps={{
startAdornment: (<InputAdornment position="start">
<Search />
</InputAdornment>)
}}
onChange={handleSearch}
/>
With this, I now need a means of being able to search on any of the columns within this data table, i.e, Calories, Carbs or Protein and not just on the Food.
I was thinking of proving the user a drop-down selection list of all the available columns and then being able to use that column to perform their search but unsure how to achieve this as new to material-ui.
Can anyone please assist with how to achieve this or possibly point me to examples or other packages that still use material-ui as keen to get this going.
Here's the similar case.
Material-UI XGrid multiple select with checkbox 'select all rows checkbox' to select filtered rows only?
And if you can look through the applyFilter function, then I am pretty sure that you could implement what you want by comparing searchedVal inside a loop of keys.
That's an idea for it.
So in your case, I'd like to code this way.
const requestSearch = (originalRows: [], searchedVal: string): [] => {
return originalRows.filter((row) => {
let matches = true;
if (searchedVal) {
const properties = ['Name', 'Calories', 'Carbs', 'Protein'];
// include keys what you want to search
let containsVal = false;
properties.forEach((property) => {
if (originalRows[property].toLowerCase().includes(searchedVal.toLowerCase())) {
containsVal = true;
}
});
if (!containsVal) {
matches = false;
}
}
return matches;
});
};
I believe this is not tricky for Material-UI, just a search function in JavaScript.
Related
I have an array state for some checkboxes where I am catching the labels for those that are true (checked). Must ignore the false.
I am able to generate a list of checked checkboxes thanks to some of you in another thread. But I'm hitting another wall with the select all toggle.
const handleSwitch = (e) => {
if(e.target.checked) {
setActive(true);
const updatedCheckedState = checkedState.map(element => element.checked = true);
setCheckedState([...updatedCheckedState]);
} else {
setActive(false)
const updatedCheckedState = checkedState.map(element => element.checked = false);
setCheckedState([...updatedCheckedState]);
}
}
This function above in particular. Likewise, if I manually check all of the checkboxes inside, it needs to know that all are selescted and make the active state = true. If I can get help with at least the first part, I'm confident I can solve the other part myself.
Here's a sandbox if you want to mess around with it. Thanks
Your sandbox is quite broken. The way you are tracking checked state is internally inconsistent.)
The main culprits (in Filter.js) are:
on line 119, you treat checkedState like a dictionary, but in handleSwitch and handleOnChange you treat it like an array (but the logic inside is still non-functional for the array approach as far as I can tell.
if you want it to be an array, let it be a string-valued "checkedLabels" array, and set checked on your checkbox component to checkedLabels.includes(item.label)
if you want it to be a dictionary:
handleOnChange needs to simply toggle the currently clicked element, like so [e.target.name]: !checkedState[e.target.name]
handleSwitch needs to add an entry for every element in data, set to true or false as appropriate.
Example (codesandbox):
const handleSwitch = (e) => {
if (e.target.checked) {
setActive(true);
setCheckedState(
Object.fromEntries(data.map((item) => [item.label.toLowerCase(), true]))
);
} else {
setActive(false);
setCheckedState({});
}
};
const handleOnChange = (e) => {
setCheckedState({
...checkedState,
[e.target.name]: !checkedState[e.target.name]
});
};
<CustomCheckbox
size="small"
name={item.label.toLowerCase()}
checked={checkedState[item.label.toLowerCase()] ?? false}
onChange={handleOnChange}
/>
EDIT from OP
I tweaked the hnadleOnChange function to
const handleOnChange = (e) => {
if (e.target.checked) {
setCheckedState({
...checkedState,
[e.target.name]: !checkedState[e.target.name]
});
} else {
const updatedCheckedState = {...checkedState};
delete updatedCheckedState[e.target.name];
setCheckedState(updatedCheckedState);
}
};
Before, it allowed for false values to be added when you unchecked a previously checked checkbox. This removes it
Edit: To do this with an array, you'll want to add to the array when checking, and remove from it when un-checking. Then do an includes to see if an individual checkbox should be checked.
Also, you can do a simple setActive(newCheckedItemLabels.length === data.length); in the handleOnChange to achieve your other requirement.
This codesandbox does everything you need with arrays instead of objects.
Notably:
const [checkedItemLabels, setCheckedItemLabels] = useState([]);
const handleSwitch = (e) => {
if (e.target.checked) {
setActive(true);
setCheckedItemLabels(data.map((item) => item.label.toLowerCase()));
} else {
setActive(false);
setCheckedItemLabels([]);
}
};
const handleOnChange = (e) => {
const newCheckedItemLabels = checkedItemLabels.includes(e.target.name)
? checkedItemLabels.filter((label) => label !== e.target.name)
: [...checkedItemLabels, e.target.name];
setCheckedItemLabels(newCheckedItemLabels);
setActive(newCheckedItemLabels.length === data.length);
};
<CustomCheckbox
size="small"
name={item.label.toLowerCase()}
checked={checkedItemLabels.includes(
item.label.toLowerCase()
)}
onChange={handleOnChange}
/>
Adding to #JohnPaulR answer. You can add useEffect hoot to achieve additional requirements you have.
if I manually check all of the checkboxes inside, it needs to know that all are selected and make the active state = true.
useEffect(() => {
const checkedTotal = Object.keys(checkedState).reduce((count, key) => {
if (checkedState[key]) {
return count + 1;
}
}, 0);
setActive(data.length === checkedTotal);
}, [checkedState]);
A full working example https://codesandbox.io/s/still-water-btyoly
community,
I'm trying to build a table where its rows are rendered based on an array of objects:
this.state = {
tableContent: [
{
id: this.state.tableContent.length,
firstname:"",
lastname:""
},
...
]
Each row displays one object. (2 columns in this case, n rows)
Each cell is also an input field, so users can manipulate the table:
<tbody>
{this.state.tableContent.map((row) => {
return(
<tr key={row.id}>
<td><input value={row.firstname} onChange={this.handleFirstNameChange}> </input></td>
...
</tr>
)
})}
</tbody>
I want each cell / input field to display the change when the user changes the input value and as such the state. Because I'm prepopulating the input field with value={row.firstname} I need to define an onChange handler function that changes the state/value of the target object's firstnameproperty.
So how does my onChange handler function look like?
I tried using spreads, but to no avail so far...
Some thoughts:
Using the standard procedure doesn't work because I have a nested state (Array of objects):
handleChange = (event) => { this.setState({ firstname: event.target.value }) }
Trying to use the spread operator results in some weird mess as well:
(this code here is somewhat wrong: maybe you can fix it?)
handleFirstNameChange = (event) => {
const {tableContent} = {...this.state};
const currentState = tableContent;
const { firstname, value } = event.target;
currentState[0] = value;
this.setState({ tableContent: currentState});
}
I appreciate any help!
edit:
The code below seems to nearly work. (thanks #Nikhil ) However now, whenever the user types into the input field, every letter they type will replace the existing letter / value of 'row.firstname'. Also, state doesn't refresh automatically so only the last-typed letter would show up / persist. What I need the input field to have is a functionality just like any casual input field.
event.persist(); seems to be needed to keep the event value.
handleFirstNameChange = (id, event) => {
event.persist();
this.setState(prevState => {
tableContent : prevState.tableContent.forEach((row) => {
if(row.id === id) { row.firstname = event.target.value}
})
})
}
input looks like this:
onChange={(event) => this.handleWNRChange(row.id,event)}
I think something like this would work.
const handleFirstNameChange = (id, event) => {
event.preventDefault();
const {tableContext} = this.state;
const myRowIndex = tableContext.findIndex((row) => row.id === id);
tableContext[myRowIndex].firstname = event.target.value;
this.setState({ tableContext });
}
This should be all you need. Just assign this method to the onChange of the input element. Like so:
onChange={(event) => this.handleFirstNameChange(row.id, event)}
May be this will help
{this.state.tableContent.map((row, index) => {
return(
<tr key={row.firstname}>
<td><input value={row.firstname}
onChange={(event)=>this.handleFirstNameChange(event, index)> </input></td>
</tr>
)
})
}
handleFirstNameChange(event, index){
this.setState((prevState => {
tableContent : prevState.tableContent.forEach((row,i)=>{
if(index === i) row.firstname = event.target.value
})
})
}
I am trying to build an universal/global search for an object in reactjs using only one text input.
I tried different approaches, including converting the string into an array and then use it to filter through my object. I managed to make the input search through all the properties, but only one at the time.
const data = [
{
name:"Italy",
title:"Best place for Pizza"
},
{
name:"USA",
title:"It is a federal state"
}
]
export class MyApp extends React.Component{
constructor(props){
super(props);
this.state = {
filteredData:[],
filter:''
}
this.handleSearch.bind(this);
}
componentDidMount(){
this.setState({
filteredData:data
})
}
handleSearch = (e) =>{
const filter = e.target.value;
this.setState({
filter
})
}
render(){
var filteredData = data;
var searchValue = this.state.filter;
filteredData = filteredData.filter(country => {
return['name', 'title'].some(key =>{
return country[key].toString().toLowerCase().includes(searchValue)
})
})
return (
<div>
<input type="search" onChange={(e) => this.handleSearch(e)}/>
{
filteredData.map(result =>{
return <p>{result.name} | {result.title}</p>
})
}
</div>
);
}
What I want is to be able to combine properties. For example: I want to be able to type in "Italy best place for..." and still get a result. I don't want to be limited to only type either "Best place for Pizza" or "Italy" to get the entry.
You can make your search do something like this:
const data = [ { name:"Italy", title:"Best place for Pizza" }, { name:"USA", title:"It is a federal state" } ]
let search = (arr, str) => {
return arr.filter(x => Object.values(x)
.join(' ')
.toLowerCase()
.includes(str.toLowerCase()))
}
console.log(search(data, 'Italy best'))
console.log(search(data, 'USA it is'))
The idea is to use Array.filter and inside get the values of the object (using Object.values) (here we assume they all are strings) and combine them (via Array.join) into one string. Then use Array.includes to search inside of it.
I'm not sure if this is best practice or not, but when I ran into a similar problem I just kept chaining my .filter() functions to each other, with each filter searching through a different property.
filteredData = filteredData.filter(country => {
return country['name'].toString().toLowerCase().includes(searchValue)
}).filter(country => {
return country['title'].toString().toLowerCase().includes(searchValue)
})
It works, but doesn't look as pretty.
I have a state of projects which is of type an array that contains the following data.
Using this data, I need to filter the user's result based on search input that matches with company_name, project_name, project_description, release_name, and release's description
this.state = {
projects: [],
searchfield: ''
};
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value })
}
<input type="text" placeholder="Search" className="form-control search-input" onChange={this.onSearchChange.bind(this)} />
render() {
return(
const filterProjects = this.state.projects.filter(data => {
// filter projects based on project's name
return data.project_name.toLowerCase().includes(this.state.searchfield.toLowerCase());
})
)
}
At the moment, I am only able to filter the result according to keywords that matches with project's name.
Hw can I use other properties or add a condition to filter data based on other properties as well?
If you want to see if a term exists in any of those fields you can use Array#some()
const filterFields = ['company_name', 'project_name', 'project_description', 'release_name', 'descrip'];
const { projects, searchfield) = this.state;
const term = searchfield.toLowerCase();
const filterProjects = projects.filter(data => {
return filterFields.some(field => data[field].toLowerCase().includes(term))
});
So I have been working hard for days and searching on how to do this. I have a Material UI table in my React App. I want to load a table where if my user has entries in the selected array it will prerender the checks in the DOM. The selected array is populated with the entries I want but my table which uses a onClick I think needs an event to trigger the DOM to render the check. This is relevant part of my table body.
<TableBody>
{this.props.competitorData.map(competitor => {
const isSelected = this.props.isSelected(competitor.key);
return (
<TableRow
hover
onClick={() => this.props.handleClick(competitor)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={competitor.key}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={isSelected} />
</TableCell>
I have a toggle that loads my table. It fills the selected the array with the subset of data I want trigger in componentWillMount. (it's 2 tables, tier1 and tier 2).
componentWillMount() {
this.renderChecks(this.props.team)
}
renderChecks(team) {
const { selected1 } = this.state;
const { selected2 } = this.state;
let newSelected1 = [];
let newSelected2 = [];
team.map(teammate => {
if (teammate.tier === "1") {
newSelected1 = newSelected1.concat(selected1, teammate.key)
} else if (teammate.tier === "2") {
newSelected2 = newSelected2.concat(selected2, teammate.key)
}
this.setState({ selected1: newSelected1 });
this.setState({ selected2: newSelected2 });
})
}
Essentially I need a way to render isSelected based of another list that is the smaller list (team is a subset of competitorData) that has the same keys. Ive tried so many things it's to many to list here. Im looking for help on what to do to make this work because nothing has worked and Im not sure what direction I should be going on in at this point. I've tried a lot of things that seem to cause instability in the render. Essentially I've tried to make the isSelected more state based but setting and resetting that state with inline functions like
{() => this.myFunctionThatUpdatesIsSelectedState(Key)}
These blow up in render. Sometimes cause an ugly infinite loop.
Update
Based on #Eld0w post below this does render my subset of checks.
checkKeys(val) {
return this.props.team.some(teammate => {
return val.key === teammate.competitorKey;
});
}
getCompetitors = () => {
const { competitorData, team } = this.props;
return competitorData.map(
value => ({
value,
isSelected: this.checkKeys(value)
})
)
}
Tables looks like this now.
<TableBody>
{this.getCompetitors().map(competitor => {
console.log('MYCOMPETITOR2::', competitor);
return (
<TableRow
hover
onClick={event => this.props.handleClick(event, competitor.value)}
role="checkbox"
aria-checked={competitor.isSelected}
tabIndex={-1}
key={competitor.value.key}
selected={competitor.isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={competitor.isSelected} />
</TableCell>
There is small issues I didn't see coming. Now my table renders only the preselected checks since im not using my previous isSelected function which was:
isSelected1 = key => this.state.selected1.indexOf(key) !== -1;
Basically i need to render the existing checks but maintain the standard isSelected function somewhere in the process as well. If I think of something or post anything about it I'll update here as well. Further input is obviously welcome.
I think i need to load my team into my selected array then run my standard isSelected function. But this is where I seem to run into trouble since that is state based. Render goes crazy on me.
Final Update
So it was late last night. I just needed to change the criterion to make this whole thing work. I load my team array in the local state selected array. Then performed isSelected property check on my competitor. Now it loads my preselected and the user can then edit selects in the table from that point.
Final Solution
Load the preselect team into the local selected state array.
componentWillMount() {
this.renderChecks(this.props.team);
}
I have tiered tables. That is just some business logic (not important here). teammate.competitorKey is the key I store that is same key as the larger table, which is competitorData. I need that to get the compares to work.
renderChecks(team) {
const { selected } = this.state;
let newSelected = [];
team.map(teammate => {
if (teammate.tier === '1') {
newSelected = newSelected.concat(selected, teammate.competitorKey)
this.setState({ selected: newSelected });
}
})
}
getCompetitor can now just verify the value key exist in the array using includes
getCompetitors = () => {
const { competitorData, team } = this.props;
console.log('THISSTATESELECTED:::', this.state.selected)
return competitorData.map(
value => ({
value,
isSelected: this.state.selected.includes(value.key)
})
)
}
And Final Table looks like
<TableBody>
{this.getCompetitors().map(competitor => {
return (
<TableRow
hover
onClick={event => this.handleClick(event, competitor.value)}
role="checkbox"
aria-checked={competitor.isSelected}
tabIndex={-1}
key={competitor.value.key}
selected={competitor.isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={competitor.isSelected} />
</TableCell>
I know this is a lot of writing but is spent a lot of time trying to get all this working. I hope it helps someone looking to do. I will look into making this more redux worth and possibly going the reselect route to optimize but for now im going to enjoy a working table for a day. Thank you again #Eld0w !!
So basically, you want to add an isSelected props to your competitors array depending on another array's values. Avoid using state, it's only props combinations.
The straightforward solution
Instead of mapping directly your competitor's array, map on a function returning an array.
getCompetitors = () => {
const { competitors, team } = this.props;
return competitors.map(
competitor => ({
...competitor,
isSelected: // criterion
})
)
}
Basically, what this does is destructuring the object and adding a new property isSelected thanks to the spread operator ... (ES6)
In your render then call this.getCompetitors().map(competitor => ...) instead of this.props.competitors.
Optimize this solution
You will want to use a plugin such as reselect to avoid any useless render operation.
const competitorSelector = createSelector(
props => props.competitors,
props => props.team,
(competitors, team) => competitors.map(
competitor => ({
...competitor,
isSelected: // criterion
})
)
)
and then use it like this in your render :
this.competitorSelector(this.props)
You will also need to use competitor.isSelected instead of isSelected since the property is now part of your competitor's properties.