Search filtering & dropdown filtering in reactjs - javascript

In the below render method, I am performing search filter & dropdown filter on the cards. Searching & dropdwon filtering are working fine, but the problem is, 1st time when my component gets render, I do not get any Card sen on the screen. I see the card on the screen only when I enter some value in my dropdown .Can anyone help me with me whats wrong in my code, and how to show Cards first and then perform filtering & searching there..
Also If I remove .filter((data) => data === this.state.filter) , I'll be able to render the data as component gets render, but then I wont be allow to perform dropdwon filter. I think this filter is causing the issue, but not sure how to fix it, as well as to perform search & dropdown filtering
searchSpace = (event) => {
this.setState({
search: event.target.value,
});
};
sortOptions = [
{value: "ALL", label: "All"},
{ value: "Iphone", label: "Iphone" },
{ value: "Samsung", label: "Samsung" },
];
getSelectedItem = (items, selectedValue) => {
return items.find((item) => item.value === selectedValue);
};
dropdown = (event) => {
console.log("filter", event.selectedItem.value);
this.setState({
filter: event.selectedItem.value,
});
};
render() {
const { details} = this.state;
const items = this.state.details
.filter((data) => {
if (this.state.search == null) return data;
else if (data.includes(this.state.search)) {
return data;
}
})
const data = items
.filter((data) => data === this.state.filter)
.map((data) =>
<>
<ShareCard></ShareCard>
</>
);
return (
<Search placeHolderText="Search" onChange={(e) => this.searchSpace(e)}/>
<Dropdown id="filtering items={this.sortOptions}
onChange={(e) => this.dropdown(e)}
selectedItem={this.getSelectedItem(
this.sortOptions,
this.state.filter
)}
/>
<div>{data}</div>
)

Since you leave the this.state.filter initially undefined thats why at the first render you don't see any results.
you should either add a default filter to the initial state:
state: {
filter: "<an initial value based on your data>"
}
or apply the filter when it is set :
const data = items
.filter((data) => this.state.filter && (data === this.state.filter) ? true: false)

const items = this.state.details
.filter((data) => {
if (this.state.search == null || this.state.search == "" || this.state.search == undefined) return data;
else if (data.includes(this.state.search)) {
return data;
}
})

Related

How to push elements to react hooks state array

I am using react-bootstrap-table2 to make HTML tables, I am using a checkbox inside my table to delete the items.
SO as per This link, it is mentioned how to get the selected row and then do the next part, here what I am doing is when I click on any checkbox row gets selected, and if I again select any row that I am adding to the array into my state like below
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
} else {
// here i need to do something
}
},
My issue is when I unselect the row that value is not getting deleted from my array, and at the time of delete, I have all the data which I have selected once.
One more thing which is related to this is when I check any row I want to show delete button and when none of the rows is checked I want to hide the delete button, here select is giving me the boolean value for that, but it is working for each select once I am selecting multiple rows it shows up, but when I unselect any one of them the delete button hides
What I have done for that is something like below
setrowSelect((rowSelect) => [...rowSelect, isSelect]); // this one is inside onSelect callback given by the react-bootstrap-table2 library
{rowSelect && (
<button className="btn Secondary_Button_with_Icon">
<i className="ri-upload-cloud-line ri-lg"></i>Register
</button>
)}
My full working code
Use a filter method inside your else block to remove that unselected element from the array
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
} else {
setrowData((rowData) => rowData.filter((x,i)=>i!==rowIndex))
}
setrowSelect((rowSelect) => [...rowSelect, isSelect]);
},
Note that, you don't need to maintain another state for controlling visibility of Delete button.
You can perfectly hide/show Delete based on rowData state.
Also the code you wrote for handling selected rows works perfectly well. Just get rid of rowSelect state and its handlers.
And update the rendering of your Delete button based on contents of your rowData as:
{
rowData.length ? (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)
: null
}
This is the forked sandbox from yours.
try to change onSelect function like this
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
rowSelect.push(true);
setrowSelect(rowSelect);
} else {
setrowData((rowData) => rowData.filter((x, i) => i !== rowIndex));
rowSelect.pop();
setrowSelect(rowSelect);
}
}
Here is one way to implement what you want :
1.Keep your data in one object, and add an id and isSelect fields
const data = [
{
id: "id-1",
fname: "john",
lname: "smith",
isSelect: false
},
{
id: "id-2",
fname: "steve",
lname: "warn",
isSelect: false
},
{
id: "id-3",
fname: "michel",
lname: "clark",
isSelect: false
}
];
2.pass this data to useState :
const [rowData, setrowData] = useState(data);
3.onSelect : just find the row by id and set isSelect field
onSelect: (row, isSelect, rowIndex, e) => {
setrowData((rows) => {
return rows.map((r) => {
if (r.id !== row.id) {
return r;
}
return { ...r, isSelect };
});
});
},
4.onSelectAll set isSelect on all rows
onSelectAll: (isSelect, rows, e) => {
setrowData((rows) => {
return rows.map((row) => {
return { ...row, isSelect };
});
});
}
5.for Delete_device just filter the data that is not selected :
const Delete_device = () => {
setrowData((rows) => {
return rows.filter((row) => !row.isSelect);
});
};
6.for the delete button, get the selected rows and count them, if the count is > 0 then show the button :
const selectedRows = rowData.filter((row) => row.isSelect);
return (
<div className="App">
{selectedRows.length > 0 && (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)}
7.Pass the state data to BootstrapTable
<BootstrapTable
bootstrap4
keyField="fname"
data={rowData}
columns={tableData[0].columnsData}
selectRow={selectRow}
/>
Complete example
I updated your state to use your data and removed the select array from your select logic. I also optimized it a bit. Its minor change from your codesandbox sample. Also, I recommend you use ids.
import React, { useState, useMemo } from "react";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
import BootstrapTable from "react-bootstrap-table-next";
import "react-bootstrap-table-next/dist/react-bootstrap-table2.min.css";
let tableData = [
{
rowsData: [
{
fname: "john",
lname: "smith"
},
{
fname: "steve",
lname: "warn"
},
{
fname: "michel",
lname: "clark"
}
],
columnsData: [
{
dataField: "fname",
text: "First name",
sort: true
},
{
dataField: "lname",
text: "last Name",
sort: true
}
]
}
];
export default function App() {
const [rowData, setrowData] = useState(tableData[0].rowsData);
const [rowSelect, setrowSelect] = useState([]);
const selectRow = useMemo(
() => ({
mode: "checkbox",
clickToSelect: false,
classes: "selection-row",
onSelect: (row, isSelect, rowIndex, e) => {
setrowSelect((rowData) =>
isSelect
? [...rowData, row]
: rowData.filter(
({ fname, lname }) => row.fname !== fname && row.lname !== lname
)
);
// if (isSelect === true) {
// setrowSelect((rowData) => [...rowData, row]);
// } else {
// console.log("onSelect", rowIndex, row, isSelect);
// setrowSelect((rowData) =>
// rowData.filter(
// ({ fname, lname }) => row.fname !== fname && row.lname !== lname
// )
// );
// }
},
onSelectAll: (isSelect, rows, e) => {
if (isSelect === true) {
setrowSelect(rows);
} else setrowSelect([]);
}
}),
[]
);
const Delete_device = () => {
console.log("Delete device", rowData, rowSelect);
if (rowSelect.length < 1) return;
setrowData((data) =>
data.filter(
({ fname, lname }) =>
rowSelect.find((s) => s.fname === fname && s.lname === lname) == null
)
);
setrowSelect([]);
};
console.log("App", rowData, rowSelect);
return (
<div className="App">
{rowData.length > 0 && (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)}
<BootstrapTable
bootstrap4
keyField="fname"
data={rowData}
columns={tableData[0].columnsData}
selectRow={selectRow}
/>
</div>
);
}
https://codesandbox.io/s/react-bootstrap-table-x-wus5r?file=/src/App.js

Need a Advanced Search Filter - React native

I have a SearchBar and a simple FlatList. My logic for search filter is working fine but that is fine for simple search.
Example:
If my FlatList has three items
Visiting hour.
He knew the visit hour.
visit and hour.
In my SearchBar if i search "visit h" it will render only item 2 -> 2. He knew the visit hour.
I want it to render all 3 items when i search "visit h"
My Search filter should look for every item that includes "visit" and "h".
It should also include "visit" in "visiting"
How can i capture this type of filter? Is there any js Library that can be used? Any efficient way to execute this search filter?
My current code is below:
export default class HomeComponent extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: true,
notifications: [],
query: '',
temp: []
}
};
componentDidMount() {
fetch('https://140xya6a67.execute-api.ap-southeast-1.amazonaws.com/dev/', {
method: 'GET',
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
notifications: responseJson,
notificationRead: false,
temp: responseJson,
})
})
};
goToDetails() {
return this.props.navigation.navigate(Details);
}
renderItem({item}) {
return <NotificationItem item={item}
onPress={() => {this.goToDetails()}}/>
}
handleSearch(text) {
const newData = _.filter(this.state.temp, (item) => {
const itemData = item.Desc ? item.Desc.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
notifications: newData,
query: text,
});
}
renderContent() {
let {notifications} = this.state;
return (
<View>
<SearchBar placeholder='type here...' lightTheme round value={this.state.query}
onChangeText={(text) => {this.handleSearch(text)}}/>
<FlatList
keyExtractor={(item, id) => item.id}
data={notifications}
renderItem={this.renderItem.bind(this)}
/>
</View>
);
}
render() {
let {fill, container} = styles;
return (
<View style={[fill, container]}>
{this.renderContent()}
</View>
);
}
}
You are describing full-text search, or even fuzzy search.
If you have a small list of static data, you can do it purely on frontend during run time with library like fuse.js.
For dynamic data, you need a backend to tokenize and prepare the data beforehand. Frontend then just send what the user has typed, and get feed the search result from backend.
You can build that yourself if you use a modern RDBMS like PostgreSQL or MSSQL. Or use services or tools more purposefully built for this problem, like algolia or Elasticsearch.
You can write simple filter of your own, like below (not tested):
handleSearch(text) {
if (!text || text.length === 0) {
this.setState({
notifications: this.state.temp,
query: text,
});
return;
}
const newData = _.filter(this.state.temp, item => {
const itemData = item.Desc ? item.Desc.toUpperCase() : ''.toUpperCase();
const textParts = text.toUpperCase().split(' ');
let shouldInclude = true;
for (let i = 0; i < textParts.length; i++) {
const part = textParts[i];
shouldInclude = shouldInclude && (itemData.indexOf(part) > -1);
if (!shouldInclude) {
return false;
}
}
return true;
});
this.setState({
notifications: newData,
query: text,
});
}

Select or unselect check boxes - ReactJS

I have managed to create my function to select a single or multiple boxes. On the single ones I don't have any issue, I mean when you click a box it is checked or unchecked.
But it is not the same case with the select all. Any ideas how can I fix that? (I am using material-ui library for my boxes, but basically they are simple HTML input)
Select all component and function:
<Checkbox
name="checkboxes"
checked={this.state.allCheckboxes}
onChange={this.handleAllCheckboxes}
indeterminate
/>Select All
Function:
handleAllCheckboxes = (e) => {
let responseObj = this.state.items;
let prepareObj = {};
if(e.target.checked){
//to do add loop to check all check box
responseObj.forEach(function(item){
if(item.documentId !== null && item.documetNumber !== null ){
prepareObj[item.documentId] = item.documentNumber;
// this.refs.documentId
}
});
let toSee = Object.keys(prepareObj).length > 0 ? true : false;
this.setState({
docList: prepareObj,
visible: toSee
})
let checkboxes = document.getElementsByName('DocCheckbox')
checkboxes.forEach(function(checkbox){
checkbox.checked = checkbox.checked
})
console.log(checkboxes)
} else {
//to do add loop to uncheck all check box
this.setState({
prepareObj: {}
})
}
console.log(prepareObj);
};
Select single component and function:
<Checkbox
name='DocCheckbox'
type='checkbox'
color='default'
value={JSON.stringify({ documentId: rowData.documentId, documentNumber: rowData.documentNumber })}
onClick={this.handleCheckboxClick}/>
Function:
handleCheckboxClick = (e, id) => {
if (id) {
} else {
let parsedVal = JSON.parse(e.target.value);
// console.log(e.target.value)
let newDocList = { ...this.state.docList };
if (e.target.checked) {
this.setState({
singleCheckbox:true
})
newDocList[parsedVal.documentId] = parsedVal.documentNumber;
} else {
delete newDocList[parsedVal.documentId];
this.setState({
singleCheckbox:false
})
}
let toSee = Object.keys(newDocList).length > 0 ? true : false;
this.setState(
{
docList: newDocList,
visible: toSee
},
() => {
console.log(this.state.docList);
}
);
}
};
UPDATE
Answer of my code based to the reply of #norbitrial
( The answer is based on my properties , calls and data so feel free to modify it for your purpose )
Step 1 - Create a constructor to maintain your data and global checked state
constructor(props) {
super(props);
this.state = {
docList: {},
checked: false,
hasToCheckAll: false
}
}
Step 2 - Create functions to handle single and multiple checkboxes
Handle single checkbox
handleCheckboxClick = (clickedItem) => {
console.log(clickedItem)
// let parsedVal = JSON.parse(e.target.value);
let newDocList = { ...this.state.docList };
if (!clickedItem.checked) {
newDocList[clickedItem.documentId] = clickedItem.documentNumber;
console.log(newDocList)
} else {
delete newDocList[clickedItem.documentId];
}
let toSee = Object.keys(newDocList).length > 0 ? true : false;
console.log(toSee)
this.setState(
{
docList: newDocList,
visible: toSee
}, ()=>{
console.log(newDocList)
});
const updatedArray = this.state.items.map((item) => {
item.checked = item.documentId === clickedItem.documentId ? !item.checked : item.checked;
return item;
});
this.setState({
items: updatedArray,
});
};
Handle all checkboxes
handleAllCheckboxes = (e) => {
const hasToCheckAll = !this.state.hasToCheckAll;
const updatedArray = this.state.items.map((item) => {
item.checked = hasToCheckAll;
return item;
});
console.log(updatedArray)
let responseObj = this.state.items;
let prepareObj = {};
if (e.target.checked) {
//to do add loop to check all check box
responseObj.forEach(function (item) {
if (item.documentId !== null && item.documetNumber !== null) {
prepareObj[item.documentId] = item.documentNumber;
}
});
let toSee = Object.keys(prepareObj).length > 0 ? true : false;
console.log(toSee)
this.setState({
docList: prepareObj,
// allCheckboxes: true,
items: updatedArray,
hasToCheckAll,
visible: toSee
})
let checkboxes = document.getElementsByName('checkAll')
checkboxes.forEach(function (checkbox) {
checkbox.checked = e.target.checked
})
console.log(checkboxes)
} else {
console.log(updatedArray)
this.setState({
docList: {},
hasToCheckAll:false
})
}
};
Step 3 - Insert the state of the checked boxes inside into the object . And loop over them ( again this is coming from the response from my Back End so for everyone this step will be different )
.then((response) => {
// handle success
let dataItem = response.data.bills;
let prepareDataItem = [];
dataItem.forEach(function (item) {
item.checked = false;
prepareDataItem.push(item);
})
Step 4 - Render the checkboxes (these checkboxes are based on material-ui library , but it can work for a simple inputs also)
<Checkbox
name='DocCheckBox'
type='checkbox'
checked={rowData.checked}
color='default'
value={JSON.stringify({ documentId: rowData.documentId, documentNumber: rowData.documentNumber })}
onChange={() => this.handleCheckboxClick(rowData)}/>
<Checkbox
name="checkboxes"
checked={this.state.hasToCheckAll}
onChange={this.handleAllCheckboxes}
indeterminate
/>Select All
I would change a bit how you are handling these things in the application.
Technically you are manipulating the DOM directly, instead of leaving it to React with its states. In this case there is a definitely better way to handle checkbox states in the UI.
The solution:
1. Changed default state:
Let me state that I don't have the real data structure what you have so I have the following in the constructor just for the representation:
constructor(props:any) {
super(props);
this.state = {
items: [
{ documentId: 1, documentNumber: 1234, checked: false },
{ documentId: 2, documentNumber: 1235, checked: false },
{ documentId: 3, documentNumber: 1236, checked: false },
],
hasToCheckAll: false,
}
}
As you can see the items have checked property in order to handle them in the state.
2. Rendering the checkboxes differently
In the render function I have changed couple of things:
render() {
return (
<div>
<Checkbox
name="checkboxes"
checked={this.state.hasToCheckAll}
onChange={() => this.handleAllCheckboxes()}
indeterminate
/>Select All
{this.state.items.map((item:any) => {
return <div key={item.documentNumber}>
<Checkbox
name="DocCheckbox"
color="default"
checked={item.checked}
value={JSON.stringify({ ...item.documentId, ...item.documentNumber })}
onChange={() => this.handleCheckboxClick(item)}/> {item.documentNumber}
</div>
})}
</div>
)
}
3. Handling checkbox state is also changed:
Take a look at the following handlers:
handleAllCheckboxes = () => {
const hasToCheckAll = !this.state.hasToCheckAll;
const updatedArray = this.state.items.map((item:any) => {
item.checked = hasToCheckAll;
return item;
});
this.setState({
...this.state,
items: updatedArray,
hasToCheckAll: hasToCheckAll,
});
};
handleCheckboxClick = (clickedItem:any) => {
const updatedArray = this.state.items.map((item:any) => {
item.checked = item.documentId === clickedItem.documentId ? !item.checked : item.checked;
return item;
});
this.setState({
...this.state,
items: updatedArray,
});
};
The result:
Of course you can extend this example with your data manipulation if there is a further need. The solution has been built with TypeScript - I had a project opened with that - but you can remove types where it has been added.
The above example works like charm, I hope this helps!

How to add an arrow to menu elements that have children?

I am trying to add a FontAwesome arrow next to each item in my menu that has children (i.e. I want to indicate that you can click the element to display more data within that category). The menu is populated with json data from an API, and because it is so many nested objects, I decided to use recursion to make it work. But now I am having trouble adding an arrow only to the elements that have more data within it, instead of every single element in the menu.
Does anyone have an idea of how I could change it so the arrow only shows up next to the elements that need it? See below for image
class Menu extends React.Component {
state = {
devices: [],
objectKey: null,
tempKey: []
};
This is where I'm currently adding the arrow...
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} <FontAwesome name="angle-right"/> </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
handleDisplayNextLevel = key => {
this.setState(prevState => ({
objectKey: {
...prevState.objectKey,
[key]: !this.state.objectKey[key]
}
}));
};
initializeTK = level => {
Object.entries(level).map(([key, value]) => {
const newTemp = this.state.tempKey;
newTemp.push(key);
this.setState({ tempKey: newTemp });
this.initializeTK(value);
});
};
initializeOK = () => {
const { tempKey } = this.state;
let tempObject = {};
tempKey.forEach(tempKey => {
tempObject[tempKey] = true;
});
this.setState({ objectKey: tempObject });
};
componentDidMount() {
axios.get("https://www.ifixit.com/api/2.0/categories").then(response => {
this.setState({ devices: response.data });
});
const { devices } = this.state;
this.initializeTK(devices);
this.initializeOK();
this.setState({ devices });
}
render() {
const { devices } = this.state;
return <div>{this.createMenuLevels(devices)}</div>;
}
}
This is what it looks like as of right now, but I would like it so items like Necktie and Umbrella don't have arrows, since there is no more data within those items to be shown
You could check in the map loop from createMenuLevels if the value is empty or not and construct the div based on that information.
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
//check here if childs are included:
var arrow = value ? "<FontAwesome name='angle-right'/>" : "";
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} {arrow} </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
Instead of just checking if the value is set you could check if it is an array with: Array.isArray(value)

Only filter Array for the defined parameters. ignore empty parameters

i have an array of projects, which can be filtered by three select dropdowns. once the user selects an option the option will be passed in the components state through the store. i just dont know how to handle the cases where one of the filter options is empty and an empty filter option is passed. also i would like to have the option to show all and ignore the filter. I just get the or logic to work like to be able to only filter for years and if i filter places or types the years already set getting ignored but i would also like to filter all three.
export class Projects extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
initialItems: props.data.projects.edges,
items: null,
filterPlace: '',
filterYear: '',
filterType: '',
};
this.placesOptions = new Set();
this.yearsOptions = new Set();
this.typesOptions = new Set();
}
componentWillMount(){
const { initialItems } = this.state
this.setState({items: initialItems})
initialItems.map((item) => {
this.placesOptions.add(item.node.categories_names[0].name)
this.yearsOptions.add(item.node.categories_names[1].name)
this.typesOptions.add(item.node.categories_names[2].name)
})
}
// TODO: FIX BUG ON RELOAD ALL EMPTY
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.filterProjects()
}
}
filterProjects(){
const { filterPlace, filterYear, filterType } = this.props;
let updatedList = this.state.initialItems;
updatedList = updatedList.filter((item) => {
const itemFilterCategory = item.node.categories_names
const placeQueryString = itemFilterCategory[0].name.toString().toLowerCase()
const yearQueryString = itemFilterCategory[1].name.toString().toLowerCase()
const typeQueryString = itemFilterCategory[2].name.toString().toLowerCase()
return (
filterPlace !== "" && placeQueryString.search( filterPlace.toLowerCase()) !== -1 ||
filterYear !== "" && yearQueryString.search( filterYear.toLowerCase()) !== -1 ||
filterType !== "" && typeQueryString.search( filterType.toLowerCase()) !== -1
)
});
this.setState({items: updatedList});
}
render() {
const { location, data } = this.props;
const { items } = this.state;
const { page } = data;
return (
<MainLayout location={location}>
<TopNavigation />
<Header
siteBrand={config.siteBrand}
siteSubtitle={page.title}
isProjectArchive
/>
<ConnectedProjectFilter
changeHandlerSearch={(e) => this.searchProjects(e)}
placesOptions={this.placesOptions}
yearsOptions={this.yearsOptions}
typesOptions={this.typesOptions}
/>
<ProjectArchiveListing projects={items} />
</MainLayout>
)
}
}
const mapStateToProps = state => ({
filterPlace: state.filterPlace,
filterYear: state.filterYear,
filterType: state.filterType,
});
const mapDispatchToProps = dispatch => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Projects)
If you want any empty input to match (pass through) every item then I believe this is the condition you want:
(filterPlace === "" || placeQueryString.search( filterPlace.toLowerCase()) !== -1) &&
(filterYear === "" || yearQueryString.search( filterYear.toLowerCase()) !== -1) &&
(filterType === "" || typeQueryString.search( filterType.toLowerCase()) !== -1)
To describe it in words, you want each passing item to be valid with respect to every filter. To validate with a filter the filter must allow all (be empty) or the item must match the filter.

Categories