I have implemented search filter in reactjs, but whenever I add space in the string, it doesn't show the existing product also.
For eg: I have Values like: "Iphone details", "Samsung phone deatils". If I enter "Iphone", I'll get the searched string as "Iphone details", but If i hit "space" after "IPhone", I'll not get the "Iphone details" but will get no result found. Can anyone help me in making optimized search for my react app.
const searchFilter = this.state.details.filter(
(data) => {
if (this.state.searchStr == null) return true;
else if (
data.name.includes(this.state.searchStr) ||
data.title.includes(this.state.searchStr)
) {
return data;
}
}
);
Can anyone help me with what's wrong with the code.
When doing comparison between saved values and user typed value, it is good to bring them to the same level, as much as you possibly can. This means that we could run the user's search and the item's value through a few normalizers. Take a look at the following modified code:
const searchFilter = this.state.details.filter(
(data) => {
if (this.state.searchStr == null) return true;
else if (
data.name.toLowerCase().trim().includes(this.state.searchStr.trim().toLowerCase()) ||
data.title.toLowerCase().trim().includes(this.state.searchStr.trim().toLowerCase())
) {
return data;
}
}
);
Functions I've added are:
.toLowerCase()
.trim()
What these do is normalize both the search input and data.name and data.title to be searched in lowercase, and trim function removes any whitespace for easier comparison.
Related
I reformatted this question to correctly display the issue and show my previous attempts to get my desired results.
Below is an NBA API response from rapid.api This specific response (https://api-nba-v1.p.rapidapi.com/games/live/) spits out the current NBA Games that are live/ongoing right now.
I'm using this API and various responses in other, straightforward ways to retrieve NBA information. My script is designed for Discord.
In this Discord Server I have, we make channels for every NBA game so that users can chat about it. I have been trying to make a score command showing the current game score.
My issue/goal:
I've been struggling to find a way to;
Match the channel name via the nickName of a team (an example of a game channel name would be: lakers-vs-nets), which will allow me to make sure that I get the score for the correct game
Retrieve the score from both the home and away teams
Print the score from both the home and away teams.
I'm unfamiliar with APIs and trying to get better with them, and I have learned more creative ways to use Javascript. So any help and explanations with this issue would be greatly appreciated; thank you.
"api":{
"status":200
"message":"GET games/live"
"results":4
"filters":[
0:"seasonYear"
1:"league"
2:"gameId"
3:"teamId"
4:"date"
5:"live"
]
"games": [
0: {
"seasonYear":"2021"
"league":"standard"
"gameId":"10187"
"startTimeUTC":"2022-01-13T00:30:00.000Z"
"endTimeUTC":""
"arena":"Madison Square Garden"
"city":"New York"
"country":"USA"
"clock":"1:35"
"gameDuration":"2:05"
"currentPeriod":"4/4"
"halftime":"0"
"EndOfPeriod":"0"
"seasonStage":"2"
"statusShortGame":"2"
"statusGame":"In Play"
"vTeam":{
"teamId":"8"
"shortName":"DAL"
"fullName":"Dallas Mavericks"
"nickName":"Mavericks"
"logo":"https://upload.wikimedia.org/wikipedia/fr/thumb/b/b8/Mavericks_de_Dallas_logo.svg/150px-Mavericks_de_Dallas_logo.svg.png"
"score": {
"points":"82"
}
}
"hTeam":{
"teamId":"24"
"shortName":"NYK"
"fullName":"New York Knicks"
"nickName":"Knicks"
"logo":"https://upload.wikimedia.org/wikipedia/fr/d/dc/NY_Knicks_Logo_2011.png"
"score":{
"points":"121"
}
}
1: {
"seasonYear":"2021"
"league":"standard"
"gameId":"10189"
"startTimeUTC":"2022-01-13T02:00:00.000Z"
"endTimeUTC":""
"arena":"Vivint Arena"
"city":"Salt Lake City"
"country":"USA"
"clock":"8:08"
"gameDuration":"1:46"
"currentPeriod":"4/4"
"halftime":"0"
"EndOfPeriod":"0"
"seasonStage":"2"
"statusShortGame":"2"
"statusGame":"In Play"
"vTeam":{...}
"hTeam":{...}
]
}
}
vTeam and hTeam are collapsed here to condense the code but it should give you an idea of the response, as it is nearly identical to the one prior, just different teams, score etc.
Here's some code I have tried so far:
function iterationObject(obj) {
for(prop in obj) {
// If Object
if (typeof(obj[prop]) == "object"){
// Push
iterationObject(obj[prop]);
} else {
/* This only seems to work if I run solely search for the keys and not the values. So for example, this would work:
if (prop == "nickName" || prop == "shortName"){
console.log(prop + ': ', obj[prop])
}
^ This would work and print the values from anything matching nickName and shortName keys (so in this case, it would print every nickName and shortName it found.*/
if (prop == "nickName" && obj[prop] == "Knicks"){
console.log(prop + ': ', obj[prop])
// ^ This is the last thing that I have tried, but has been returning undefined.
}
}
}
}
case 'gamescore':
var gamescoreurl = "https://api-nba-v1.p.rapidapi.com/games/live/";
axios.get(gamescoreurl, {
headers: {
"x-rapidapi-key": apikey,
"x-rapidapi-host": apihost
}
}).then(response=> {
/* Something I tried with .find, only seems to search surface level with the response. What I mean by that is,
I can dive into games API response, but I can't go games.hTeam with .find
var firstchannelpart = message.channel.name
var gamechannelfinder = firstchannelpart.split("-")[0];
var gameapiresp= response.data.api.games
const hscore = gameapiresp.find(el => {
return el.nickName== gamechannelfinder
})
console.log(hscore)
*/
// My latest attempt, which works retrieving specific values that match using prop as a var in iterationObject. Also, the item variable returns as undefined here, but iterationObject will print from the console what it is supposed to.
tidobj.filter(item => {
iterationObject(item)
})})
break;
You can see I put in some comments, those are solely for this thread to help understand my previous attempts, but I feel that one of them might be just on the brink of being right.
With the API Results listed above you could do something like,
// Set up our sample vars
let channelName = "Mavericks-vs-Knicks";
let gamescoreurl = "https://api-nba-v1.p.rapidapi.com/games/live/";
// Make Axios Call
axios.get(gamescoreurl, {
headers: {
"x-rapidapi-key": apikey,
"x-rapidapi-host": apihost
}
}).then(results => {
// get array of team names to pass to 'LogGameScores'
let teamNickNames = GetTeamsFromChannelName(channelName);
// Log the game scores
LogGameScores(teamNickNames, results?.games);
})
This is a simple function to get the team names from the channel name.
function GetTeamsFromChannelName(name = "channelName") {
return name.split("-vs-"); // Split the team Nick Names from the channel name
}
And the a method for getting / logging the scores
function LogGameScores(teams = [], games = null) {
// verify params and log errors found
if (games == null)
return console.log("An error occured while trying to log games scores");
if (teams.length < 2)
return console.log("An error occured while trying to log games scores");
try {
// filter the results to just the game or games representing the teams
// we want from the returned results
let game = games.filter((el) => {
return (
// Check for the game that matches both teams. You are likely able
// to match by only one team name but just in case
// Check for the teams name in the visiting team section
(el.vTeam.nickName.toLowerCase() == teams[0].toLowerCase()
|| el.vTeam.nickName.toLowerCase() == teams[1].toLowerCase())
// Check for the teams name in the home teams section
&& (el.hTeam.nickName.toLowerCase() == teams[0].toLowerCase()
|| el.hTeam.nickName.toLowerCase() == teams[1].toLowerCase())
);
});
// log the scores for the home team and visiting team to the console
game.forEach((el) => {
console.log(`(Home) ${el.hTeam.nickName}: ${el.hTeam.score.points}`);
console.log(`(Visitor) ${el.vTeam.nickName}: ${el.vTeam.score.points}`);
});
} catch (error) {
console.log({ error, teams, games});
}
}
I am working on a react project that uses redux forms. I've looped through the fields to check their validation requirements if need be. This stack works well - but I know I should re-visit this to place parts inside a function
if(field.validate.includes("email")) {
//email
if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values[fieldName])) {
errors[fieldName] = 'Invalid email address'
}
}
if(field.validate.includes("minLength")) {
//minLength
if (values[fieldName] !== undefined && values[fieldName].length < 3) {
errors[fieldName] = 'Must be 3 characters or more'
}
}
if(field.validate.includes("required")) {
//required
if (!values[fieldName] && typeof values[fieldName] !== "number") {
errors[fieldName] = 'Required'
}
}
I've tried to write a function that looks like this - but I don't want to break the stack.
messageHandler(field, validation, rule, message){
if(field.validate.includes(validation)) {
if (rule) {
return message;
}
}
}
From what I can see, you're trying to validate a field's content against a set of defined rules.
To me, rules are just functions that can either be successful or not. For the sake of simplicity, let's say that if they return null it means that they are successful and otherwise, we'll return an error message (just a string).
const rules = {
email: value => !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
? 'Invalid email address'
: null,
minLength: value => value !== undefined && value.length < 3
? 'Must be 3 characters or more'
: null,
required: value => !value && typeof value !== "number"
? 'Required'
: null,
};
Now, for each rule that we find in field.validate, we'll apply the corresponding rule and collect the result :
const matchingRules = field.validate
.map(formValidate => rules[formValidate])
.filter(x => x); // filter nullish values (rules that are not found)
const errors = matchingRules
.map(fn => fn(values[fieldName]))
.filter(x => x); // filter nullish values (successful rules)
Now, errors contains a list of strings describing how the field failed the different rules, and of course, if errors.length === 0, the test is successful.
You can add as many rules as you want without repeating all the ifs.
Is it acceptable in this project to bring in another lib? I like to use joi for validation. Of course, it is important to understand how validation works under the hood, but it seems like you have a pretty good grasp on that.
Here's an example of how you'd implement this with your current code:
First you would define a schema, which essentially represents your ideal end-state for your filled-in form. Below, I am saying that the form values will include an email, which will be a required string that is at least 3 characters long.
const Joi = require('joi');
const schema = Joi.object({
email: Joi.string().email().required().min(3)
})
Then, when you are ready to validate the form data:
const validation = schema.validate({ email: 'foo#bar.com' });
validation will contain values & errors (if there are any).
You can throw that schema.validate function in a useEffect that fires off whenever the user updates an input, or you can wait until the user is trying to submit the form, whatever your UI requires.
I like it because it is easy to read and write and it's quite flexible.
I'm quite new to ReactJS and work on a simple application which shows many different data (called apps). I want to implement a live free text search-filter. For that I'm using an Input Element, calling a JavaScript function if the value is changing. It's working quite good. But only for one string. If I want to filter for more words it's handled as an "AND" filter. But I want an "OR" filter.
I have the apps and filter them with a searchString. The User has to input at least three characters. If the user enters two words with three characters f.e. 'app' and 'red', I want to filter for the elements which has the words 'app' OR 'red' in the title. If min. one of the filter-strings matches, the app is shown. That's the plan.
I tried with .indexOf(), .includes() and nothing matches, even in the documentation I found nothing like an "OR" filter-search.
Here is my code, working for one string:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } ); //Only words with at least 3 chars are allowed in the array
if (searchString.length >= 3) { //Check if the minimun amount of characters is fullfilled
let allApps = this.props.applications;
let apps = allApps.filter(app =>
app.title.toLowerCase().includes(searchString)
);
this.props.updateApplications(apps);
} else {
this.clearSearch()
}
}
my Input element:
<Input
id="freeTextSearch"
className="searchInput"
onChange={this.updateSearch.bind(this)}
autoComplete="off"
action={
<Button
id="freeTextSearchButton"
name="search"
icon="search"
onClick={() => this.clearSearch()}
/>
}
placeholder="Search"
/>
Thanks for the help
Yvonne
ANSWER:
Thank you 'Silvio Biasiol'. Your Solution gave me the right hint. Now I have an 'OR' filter-search matching at least one word. The function now looks like:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } )
if (searchStringSplit.length >=1) { //Check if there is at least on word with tree letters
let allApps = this.props.applications
// If at least a word is matched return it!
let apps = allApps.filter(app => {
let containsAtLeastOneWord = false
searchStringSplit.forEach(searchWord => {
if (app.title.toLowerCase().includes(searchWord))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return app
}
);
this.props.updateApplications(apps)
} else { // user deletes manually every word
this.clearSearch()
}
}
Thanks at everyone
If you just want to match at least one word than it's pretty easy :)
let string = "My cool string"
let possibleStrings = [
'My cool string',
'My super cool string',
'Another',
'I am lon but sadly empty',
'Bruce Willis is better than Pokemon',
'Another but with the word string in it',
'Such string much wow cool']
// Split spaces
let searchString = string.toLowerCase().split(' ')
// Log the result, just wrap it in your react script
console.log(possibleStrings.filter(string => {
let containsAtLeastOneWord = false;
// If at least a word is matched return it!
searchString.forEach(word => {
if (string.toLowerCase().includes(word))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return string
}))
You are not using your searchStringSplit array. Using this array you could do the following:
const searchRegex = new RegExp(searchStringSplit.join("|"))
let apps = allApps.filter(app =>
searchRegex.test(app.title.toLowerCase())
);
You join your searchStringSplit array into a regex with the form term1|term2|term3... and match it aginst the title.
Another option would be to use the Array.prototype.some() function like this:
let apps = allApps.filter(app =>
searchStringSplit.some(searchString => app.title.toLowerCase().includes(searchString))
);
You fitler all your apps and for each app you check if it's title includes 'some' of the search strings.
trying to understand your code, suppose that searchString is "facebook twitter youtube"
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } );
//searchStringSplit is like [ "facebook", "twitter", "youtube" ]
//allApps is like ["TWITTER","INSTAGRAM"]
allApps.filter(app=> searchStringSplit.includes(app.toLowerCase()))
//returning me all apps in allApps that are includes in searchStringSplit
// so return ["TWITTER"]
not sure if it's exactly what you need...if it's not please let me know so I can change the answer...
I have a question regarding the Sitecore Item Web API.
I have this JSON extract result when I call my Sitecore Web API:
{"statusCode":200,
[...]
"Version":1,
"Fields":{
"{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}":{
"Name":"Phone number",
"Type":"Single-Line Text",
"Value":"XXXX"},
"{FA170F50-43FA-49B7-9AB1-9B4047DDBC2C}":{
"Name":"Fax",
"Type":"Number",
"Value":"YYYYYY"}
What I would like to do is to get the phone number Value field, or the Fax value field, directly, without looping through each GUID testing if the Name == "Phone Number" and then getting the Value etc...
I already have this JavaScript code but it is with this foreach loop, and it's not really fast.
function extractPhoneNumber(siteCoreFieldsList) {
var phoneNumberFieldValue;
$jq.each(siteCoreFieldsList, function (index, value) {
if (value.Name == "Phone number") {
phoneNumberFieldValue = value.Value;
}
});
return phoneNumberFieldValue;
}
Anyone has a work around or something more clean ?
Thanks a lot for your help. Regards.
In terms of a "best practice", you should be storing the field IDs somewhere any using them to get to the value.
However, given you're already creating function with the specific purpose of getting a value you could place the IDs there.
function extractFax(sitecoreFieldList){
return sitecoreFieldList['{FA170F50-43FA-49B7-9AB1-9B4047DDBC2C}'] || '';
}
function extractPhoneNumber(sitecoreFieldList){
return sitecoreFieldList['{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}'] || '';
}
I am trying to create a filtered list projection from a collection of search terms. For instance, if I have one search term, I can do something like this:
if (options.groupKey == "filtered") {
this._items = Data.getItemsFromGroup(this._group);
var query = Windows.Storage.ApplicationData.current.localSettings.values["filters"];
this._items = this._items.createFiltered(function (item) {
if (item.content.search(query) > -1) {
return true
} else {
return false
}
})
}
But what if the 'filters' local setting is a CRLF delimited list, like this:
Cisco
Microsoft
Dell
Currently, the search will compare each term to 'Cisco/nMicrosoft/nDell' which obviously won't work. content.search doesn't accept an array. Should I just do a loop in the createFiltered function somehow? That doesn't seem to be in the spirit of the projection. What is the generally accepted way to do this?
What about storing and object in the "filters" settings where every filter is a property? will that work for you?
if (options.groupKey == "filtered") {
this._items = Data.getItemsFromGroup(this._group);
var query = Windows.Storage.ApplicationData.current.localSettings.values["filters"];
this._items = this._items.createFiltered(function (item) {
return Object.keys(query).indexOf(item) > -1;
})
}
The query object would be something as follows:
{
Cisco: "",
Microsoft: "",
Dell: ""
}
Does that make sense?
edit: made a little change in the code since I believe if (query[item]) would always return false because of javascript type-casting