In react, how to use condition to create a custom search functionality? - javascript

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))
});

Related

React Material-UI Column Search Selection

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.

react-select onChange event does not render with props

i'm using the select-react library --> https://react-select.com/home
i have managed to mapped options to the individual items i will need from itemList. the dropdown do register the selected option, however my end goal is such that my bar chart renders the correct data of the selected option.
/**
* Produces a filter list for item sales cards
* #param {*} props
* #returns the filter list of item names along with their ids
*/
export const ItemSelection = (props) => {
const { onChangeValue, itemList } = props;
if (itemList.length === 0) return <></>;
return (
<UncontrolledDropdown>
<Select
options={itemList.map((item) => {
return {
label: item.name,
value: item,
key: item.itemID,
};
})}
onChange={(item)=>onChangeValue(item)}
placeholder="ITEM"
/>
</UncontrolledDropdown>
);
};
itemSelection is being used here:
const [itemID = '1', setItemID] = useState('1');
const [itemName = '', setItemName] = useState('');
const [itemListID = [], setItemListID] = useState([]);
<ItemSelection
onChangeValue={(item) => {
setItemID(item.itemID);
setItemName(item.name);
}}
itemList={itemListID}
/>
onChangeValue is the one registering the option selected to the barchart. Is there anyway to map onChangeValue?
It depends what argument you're actually expecting from the parent element that uses ItemSelection. That said, it also looks like Select takes a function that takes an action of type string and the option value as the second argument. The selected value would then come in the second argument.
Something along the lines of of the below code is what you need if all you care about is the value. On that note, you will get the selected string value passed to onChangeValue which I imagine is what you want without seeing the function definition from an ancestor component.
Updated:
The select code can take the entire option object as the second parameter.
export const ItemSelection = (props) => {
const { onChangeValue, itemList } = props;
if (itemList.length === 0) return null;
return (
<UncontrolledDropdown>
<Select
options={itemList.map((item) => ({
label: item.name,
value: item.name, // note a change from your code here; it would have been an object otherwise
key: item.itemID,
}))}
onChange={(item)=>onChangeValue(item)}
placeholder="ITEM"
/>
</UncontrolledDropdown>
);
};
In the parent component
// note the changes here where I deleted the assignments
const [itemID, setItemID] = useState('1');
const [itemName, setItemName] = useState('');
const [itemListID, setItemListID] = useState([]);
<ItemSelection
onChangeValue={(option) => {
setItemID(option.key);
setItemName(option.value);
}}
itemList={itemListID}
/>

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

Searching through multiple properties in an object using only one input aka global search

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.

Updating nested state in objects through an input element in React

I am creating a form that allows orders to be updated. The input fields have to be populated with the current state of each object that I render and I'd like to be able to edit the input field. I have simplified my code to just one input field and believe that I'm able to do the majority of what I'm attempting using the following code --
class EditOrderForm extends React.Component {
...
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
renderEditOrderForm(key) {
const order = this.props.orders[key]
return (
<div key={key}>
<form >
<input type="text" name="name" value={order.data.name} placeholder="order name" onChange={(e) => this.handleChange(e, key)} />
...
</form>
</div>
)
}
render() {
return (
<div>
<h2>Edit Orders</h2>
{
Object.keys(this.props.orders).map(this.renderEditOrderForm)
}
</div>
)
}
}
*************Parent Component*************
class AppComponent extends React.Component {
import EditOrderForm from './EditOrderForm';
...
updateOrder(key, updatedOrder) {
const orders = [...this.state.orders]
orders[key] = updatedOrder;
this.setState({ orders: orders });
}
...
}
The state that's set at the parent component level is an array of objects and the data structure for the objects that I'm passing to renderEditOrderForm() has the structure --
{
data: Object,
meta: Object,
__proto__: Object
}
Where data: Object contains the key-value pairs that I'm trying to change, in this case the key name nested under data: Object (above) and I would like put it back into the array once updated/edited. I am slightly able to update the name of an order however when I try to update it (say, type an 'x') the object now has this structure --
{
data: Object,
meta: Object,
name: "John Smithx"
__proto__: Object
}
I can intuit that [e.target.name]: e.target.value is probably the culprit, however I'm completely at a loss as to how I'm supposed to access the nested key name in data: Object -- I have tried e.target.data.name, however that gives me undefined and have tried a variety of other combinations. Without using Redux (unfortunately don't have time to learn due to time constraints), does anyone know how I can access/target the key name in order to update nested in data: Object?
You need to change the field order.data.name but your code is only adding a new field to the order object. Replace
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
with
handleChange(e, key) {
const order = this.props.orders[key];
let updatedOrder = { ...order };
updatedOrder.data[e.target.name] = e.target.value;
// or if you have only 1 field called name, you can use updatedOrder.data.name = e.target.value;
this.props.updateOrder(key, updatedOrder);
}

Categories

Resources