Trouble with React/JS filter - javascript

I am trying to implement a filter function that is able to search in two separate JSON fields when a user types in a search bar. Searching the whole JSON returns errors and if I repeat this function, the two similar functions cancel each other out.
My current filter function:
let filteredOArt = origArt.filter((origAItem) => {
return origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())
});
I want to be able to have the search look within the "authors" field as well as a "description" field.
Before the React render, I have this function listening to the state:
updateSearch(event) {
this.setState({ search: event.target.value })
}
Then my search function is in an input field in the React return:
<h6>Search by author name: <input type="text" value={this.state.search} onChange={this.updateSearch.bind(this)} /></h6>

You can tweak the function a bit like this
let filteredOArt = origArt.filter((origAItem) => {
return (
(origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())||
(origAItem.description.toLowerCase().includes(this.state.search.toLowerCase())
)
)
});

You actually can do a filter for both fields.
Given you have your searchValue and your array with the objects you could filter this way:
const filterByAuthorOrDescription = (searchValue, array) =>
array.filter(
item =>
item.authors.toLowerCase().includes(searchValue.toLowerCase()) ||
item.description.toLowerCase().includes(searchValue.toLowerCase())
);
const filtered = filterByAuthorOrDescription(this.state.search, articles);
filtered will now contain an array of objects that contain your searchValue in either description or authors that you can map through.

You could use some to check if the filter is positive for at least one field :
let filteredOArt = origArt.filter(origAItem => ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase())))
Just iterate over the different field names you want to use.
Some will return true if any of the fields mentioned contains your string and avoid repetitions in your code.
Long syntax :
origArt.filter(origAItem => {
return ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase()))
})

Related

Filtering Data By Full Name React

Hi I am Trying to filter my Data By Full Name, my API takes in a first and last name and I am returning it as:
const filteredStudents = students.filter( student => {
return (student.firstName.toLowerCase().includes(search.toLowerCase()) + student.lastName.toLowerCase().includes(search.toLowerCase()));
})
which only shows a result when typing a first or last name is there a way to handle a condition for when you type in the whole name?
For Example, "Bill Gates"
You can concatenate the names first, then check if the search term is contained.
const filteredStudents = students.filter((student) => {
return `${student.firstName} ${student.lastName}`
.toLowerCase()
.includes(search.toLowerCase());
});

push dynamically coming id's and value's in javascript

The question may seem a little confusing so let me explain the situation, i have a category field, that when it's chosen, i get data from api based on that category, for example when you choose category A, you may get the id's 1 to 3 or when you chose category B, you get the id's of 8 to 11, so what i'm trying to do is create a Filter array that pushes these values with id's so when user types something like sth in input with the id of 1, this objects gets pushed to the array, something like below:
Filters = [
{
id:1,
value: sth
}
]
so what i have basically done is something like this:
this.props.Filters.map(item => (
<div className="from" key={item.id} id={item.id} >
<span className="title">{item.feature}</span>
<input type="text" placeholder="..." id={item.id} onChange={FilterPush}></input>
</div>
))
and the function is:
const Filters = []
const FilterPush = (e) => {
const filter = {
id: e.currentTarget.getAttribute("id"),
value: e.currentTarget.value
}
Filters.push(filter)
}
the problem i'm having here is every time a user is typing something an object is pushed to Filters,
so when user tries to type sth the Filter gets equal to:
Filters={
{id:1,value:s},
{id:1,value:st},
{id:1,value:sth}
}
but i want it to be equal to this
Filters={
{id:1, value:sth}
}
bear in mind that the id's are dynamic and i don't know them, and there's not just one input, i mean we can have 3 inputs with diffrent id's that should get send to the server.
Just filter out existing id:
const FilterPush = (e) => {
let id = e.currentTarget.getAttribute("id")
const filter = {
id,
value: e.currentTarget.value
}
Filters = Filters.filter(item => item.id !== id) // filter out existing id
Filters.push(filter)
}
It's very simple all you have to do is just check the pushing element's id is already in the Filters array before pushing the element.
const FilterPush = (e) => {
let id = e.currentTarget.getAttribute("id")
const filter = {
id: e.currentTarget.getAttribute("id"),
value: e.currentTarget.value
}
Filters.map(item=>{
if(id == item.id){
available = true;
}
})
if(!available){
Filters.push(filter)
}
}

ngx dropdown list get selected values

I am trying to use ngx dropdown list like this:
<ngx-dropdown-list [items]="categoryItems" id="categoriesofdata" [multiSelection]="true"
[placeHolder]="'Select categories'"></ngx-dropdown-list>
And I am getting all selected values like:
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => ({
value: item.value
}))) : '';
}
and output looks like:
[{"value":"Surname"},{"value":"Address"}]
I want to get only for example Surname instead of value and Surname.
[0].value
How Can I do this?
Should I use for loop or is better option?
I think you're almost there, in fact you're doing a little too much. Your map function should just return the value you are interested in rather than creating a new structure.
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => item.value)) : '';
}
Edit:
And as a personal preference, I would refactor to something like this:
get selectedCategories() {
if (!this.categoryItems.length) {
return '';
}
const surnames = this.categoryItems
.filter(item => item.selected)
.map(item => item.value);
return JSON.stringify(surnames);
}
I prefer to get out of a function early in the case where no further processing is required. And I would return the result of chained filter and map functions into a new surnames variable. The named variable signals the intention of the code, and keeps the array logic together.
This is just my preference though. Your code was almost there functionally.

How to search a value in an array inside another array

I have a problem of find a value in an array inside another array, and use the result to setState()
This is the initialState:
this.state =
{
initialStudents:[
{name:"str1",tags;["str","str",...],...},
{name:"str2",tags;["str","str",...],...},
...
],
students: [
{name:"str1",tags;["str","str",...],...},
{name:"str2",tags;["str","str",...],...},
...
]
}
The code i use to find the tags:
findTag = (tags, target) => {
tags.filter(tag => {
return tag.toLowerCase().search(target.toLowerCase()) !== >-1;
});
};
filterTag = e => {
let updatedList = this.state.initialStudents;
updatedList = updatedList.filter(student => {
return this.findTag(student.tags, e.target.value);
});
this.setState({ students: updatedList });
};
The filterTag does not update the students state
To solve your problem, I made a few edits and put them all in this working codesandbox example.
First, I changed your findTag function to something like this:
// pass in the tags from the student, and the target tag you're searching for.
// -> return true if 1 or more matching tag, false otherwise
findTag = (tags, targetTag) => {
// make sure you return something!
return tags.filter(tag => {
// check if current tag in arr matches target tag (case insensitive)
return tag.toLowerCase() === targetTag.toLowerCase();
}).length > 0; // check if there's 1 or more matching tag
};
Next, I updated the filterTag function in a few ways:
Immutably copy this.state.initialStudents into the local updatedList array. This is necessary so you don't mess up the current state before running this.setState!
Pass the value of the input via this.state.filterTag instead of e.target.value. This way, you'd update the filter when you click the button instead of on every time you press a key.
Here's how these changes look:
filterTag = e => {
// immutably copy initial student data
let updatedList = this.state.initialStudents
.map(student => ({
name: student.name,
tags: [...student.tags]
}))
// remove students w/out filter tag
.filter(student => {
return this.findTag(student.tags, this.state.filterTag);
});
// update state with new student list
this.setState({ students: updatedList });
};
A few other improvements I made:
Instead of manually setting data in initialStudents and students, I made them immutably copy the same data set from the const initialStudents data set. This could be done in the componentDidMount lifecycle method if you're fetching students from a database.
I fixed your student object declarations - you put tags;["str"...] which is invalid - the semicolon ; should be a normal colon :
I changed some "str" values to "str2" to make them unique between students
Let me know if you have questions about the codesandbox or anything else :D Hope it helps!

How to search in a json array, and return the result as I type in the input

So far I can only search an element of the array if I type the exact name present in my api, in this my api has an array with 20 positions and if I type exactly the name of the element I search it returns an array with 19 positions with undefined and 1 position with the array found, what I want to do and search while I type instead of searching only when I type the full name.
After my search I try to change the state of a component so that it is rendered only with the value fetched, but this does not happen, if anyone knows I am very grateful.
updated code
import data from "../sample_data/recipes.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString: data.results })
}
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var indexes = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) !== -1;
})
this.setState({ searchString : indexes });
}
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
export default App;
I suppose that you want some kind of filtering while you're typing:
can you try this ? :
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var filteredItems = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) === -1;
})
this.setState({ searchString : filteredItems });
}
Explanation :
What is requested is to not display items that contains typed letters, to do that, you can use filter items with filter method and return only the items which doesn't have the typed letters in the title (using the indexOf method).
move setState outside map.
replace map with for loop
maybe try this?
var indexes = [];
data.results.forEach(item => {
if(item.title.indexOf(myObjTitle.title)) {
indexes.push(item);
}
});
this.setState({searchString : indexes});
As I understand your question, you are trying to search the element from json Array.I just added the new key to store filterString. Here is my solution
onChange(firstName){
if(firstName != undefined){
this.setState({
nameToFilter : firstName
})
}
}
//Inside the render method
I am using nameToFilter to filter the data.
render() {
let searchString = this.state.searchString
if(this.state.nameToFilter != undefined && this.state.nameToFilter.length>0)
{
searchString = this.state.searchString.filter(item => (
item.title == this.state.nameToFilter
));
}
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={searchString} />}
</div>
</div>
</div>
);
}
It seems you are trying to filter the matching results. In which case it's probably better to use filter instead of map.
onChange(value) {
let results = [];
// Empty string is `falsy`, avoid filtering when no value was given
if (value) {
/*
Pick only one option, remove the other two, or you will
process your data three times instead of once!
*/
// Case-sensitive, exact match
results = data.filter(item => item.title === value);
// Case-insensitive, exact match, value and title are the same when lowercased
results = data.filter(item => (
item.title.toLowerCase() === value.toLowerCase()
));
// Case-insensitive, partial match, value was found *within* title
results = data.filter(item => (
item.title.toLowerCase().indexOf(
value.toLowerCase()
) !== -1
));
}
this.setState({results});
}
Additionally, if you want a single result (the first one that matches), using find instead of filter is better, because it would stop searching after the first match instead of traversing the whole array.
Map
Returns a new array with as many entries as you previously had, but you may return anything you want as the entries, this is why you get so many undefined entries right now. (Because you only return an item when the title matches, hence you implicitly return undefined when not). Map loops through the whole array.
Filter
Returns a new array with as many entries matches your test function. If the function returns true, then the item is included, it it returns false, the item will be omitted. Filter loops through the whole array.
Find
Will only retrieve the first entry that matches your test function. It will stop looping once a match is found.
Bonus
You will most likely need to learn about debouncing, which you can do with lodash.debounce.
Debouncing is a method used to prevent a function from executing many times in a short interval (so you don't unnecessarily re-render)

Categories

Resources