ReactJS - Search/Iterate through array of objects - javascript

I have an object like that:
{
"data": [
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Bobby"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
]
}
React code
import React from 'react';
import { Input } from 'antd';
import { connect } from 'dva';
const Search = Input.Search;
#connect(({ rule, loading }) => ({
rule,
loading: loading.models.rule,
}))
export default class SearchBox extends React.Component {
constructor(props) {
super(props)
this.state = {
isListLoaded: false,
resultArr: {}
}
}
performSearch(value) {
for( var i = this.props.rule.data.list.length; i--; ) {
for (var key in this.props.rule.data.list[i]) {
this.setState({resultArr: this.state.resultArr.push(i)});
}
}
}
componentDidMount() {
if (!this.state.isListLoaded) {
const { dispatch } = this.props;
dispatch({
type: 'rule/fetch'
});
this.setState({ isListLoaded: true });
}
}
render() {
return (
<div>
<Search
placeholder="Search..."
onChange={(event) => this.performSearch(event.target.value)}
style={{ width: "250px", "margin-left": "20px"}}
/>
</div>
);
}
}
My goal is very simple: I want to search through this object, and
return the entire array(s) that contains the keyword.
Example: if I search "Sar", I should get 2 objects:
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
Problem is, I get an error when I'm trying this code. I did search for previous solutions to this problem here on SO, but I can only find examples where there's only one element returned. What I want, is to get ALL the results that contain the keyword in ANY attributes (in this example, I'm returning 2 elements, not just one)
Any idea?

const { data } = {
"data": [
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Bobby"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
]
};
const keyword = "Sar";
const filtered = data.filter(entry => Object.values(entry).some(val => typeof val === "string" && val.includes(keyword)));
console.log(filtered);
It filters the entries of data Array with the following criterium: at least one of the entry's values must contain a given keyword.
Since IE doesn't yet support Object.values() and String.prototype.includes() you can use the following:
const containsKeyword = val => typeof val === "string" && val.indexOf(keyword) !== -1;
const filtered = data.filter(entry => Object.keys(entry).map(key => entry[key]).some(containsKeyword));
or polyfill these ES6 features, see more here.
To make the keyword lookup case insensitive, you can use RegExp:
const re = new RegExp(keyword, 'i');
const filtered = data.filter(entry => Object.values(entry).some(val => typeof val === "string" && val.match(re)));

Instead of looping through array simply use filter method of javascript
performSearch(value) {
const unfilteredData = this.props.rule.data.list;
const filteredDate = unfilteredData.filter((val) => {
return val.name.indexOf(val) !== -1;
});
this.setState({
resultArr: filteredDate,
})
}

performSearch(value) {
let filteredData = this.props.rule.data.list.filter(item => {
let isFiltered = false;
for(let key in item){
if(item[key].includes(value)){
isFiltered = true;
}
}
return isFiltered;
})
this.setState({resultArr: filteredData});
}

Related

TypeError: arr.map is not a function in js, and nextjs

I am getting this error, but the code was working now it's not working somehow.
TypeError: arr.map is not a function
Am I doing something wrong?
export const footerAdapter = (footerData) => {
const arr = footerData[0].attributes['items']
return arr.map((md) => {
return {
link: md.link,
title: md.title
};
});
};
Update:
I am trying yo fetch this data:
[
{
"attributes": {
"items": {
"title": "Hello world",
"link": "/Home",
"__typename": "ComponentFooterFooterItems"
},
"__typename": "Footer"
},
"__typename": "FooterEntity"
},
]
The "TypeError: map is not a function" occurs when we call the map() method on an object that is not an array.
To solve the error, console.log the value you're calling the map() method on and make sure to only call the map method on valid arrays.
const obj = {};
// ⛔️ Uncaught TypeError: map is not a function
const result = obj.map(element => {
return element + 1;
});
You can conditionally check if the value is an array by using the Array.isArray method.
const arr = 'test';
const result = Array.isArray(arr) ? arr.map(element => element + 1) : [];
console.log(result); // 👉️ []
Your data could be undefined or null and therefor it gives you an error. So you should write in a fallback:
export const footerAdapter = (footerData) => {
if (!footerData || !footerData[0] || !footerData[0].attributes || !footerData[0].attributes['items'] || footerData[0].attributes['items'].length <= 0) return []
const arr = footerData[0].attributes['items'];
return arr.map((md) => {
return {
link: md.link,
title: md.title
};
});
};
This will return an empty array if footerData[0].attributes['items'] is undefined or null;
Edit
The .map wont work because your 'items' isnt an array. If something has changed, this is what is wrong.
But you can change it to
export const footerAdapter = (footerData) => {
if (!footerData || !footerData[0] || !footerData[0].attributes || !footerData[0].attributes['items'] || footerData[0].attributes['items']) return {link: '', title: ''}
const obj = footerData[0].attributes['items']
return {
link: obj.link,
title: obj.title
}
}
Edit 2
If items for some reason sometimes are an array and sometimes are an object, you need to do some typechecking. One way to solve it is to:
export const footerAdapter = (footerData) => {
if (!footerData || !footerData[0] || !footerData[0].attributes || !footerData[0].attributes['items']) {
return [{link: '', title: ''}];
}
const arr = footerData[0].attributes['items'];
if (arr && arr[0] && arr.length > 0) {
return arr.map((md) => {
return {
link: md.link,
title: md.title
};
});
} else if (arr && arr.link && arr.title) {
return [{link: arr.link, title: arr.title}];
} else {
return [{link: '', title: ''}]
}
};
The above code is a litle ugly, but should work both if your items property is an array (like before) or an object in your example. With an handler which returns [{link: '', title: ''}] doesn't mach one of the two first strucktures.
try fixing the error using arr?.map
You can use the optional chaining
Note your items is not an array so we need more code
Here I use reduce, assuming that by [0] you only want the first entry in your array
const footerAdapter = footerData => Object.entries(footerData?.[0].attributes?.['items'] ?? {})
.filter(([key,value])=> ["link","title"].includes(key))
.reduce((acc, [key,value])=> (acc[key] = value, acc),{});
const fd1 = [{
"attributes": {
"items": {
"title": "Hello world",
"link": "/Home",
"__typename": "ComponentFooterFooterItems"
},
"__typename": "Footer"
},
"__typename": "FooterEntity"
} ];
const fd2 = [{
attributes: {}
}]
const arr1 = footerAdapter(fd1)
console.log(arr1)
const arr2 = footerAdapter(fd2)
console.log(arr2)
Array of objects
const footerAdapter = footerData => Object.entries(footerData?.[0].attributes?.['items'] ?? {})
.filter(([key,value])=> ["link","title"].includes(key))
.map(([key,value])=> ({[key]:value}));
const fd1 = [{
"attributes": {
"items": {
"title": "Hello world",
"link": "/Home",
"__typename": "ComponentFooterFooterItems"
},
"__typename": "Footer"
},
"__typename": "FooterEntity"
} ];
const fd2 = [{
attributes: {}
}]
const arr1 = footerAdapter(fd1)
console.log(arr1)
const arr2 = footerAdapter(fd2)
console.log(arr2)

How to store multiple values in to objects within an array

Problem:
I have an api and each object within the api doesn't have a value. I would like to add a unique value to each object within the array so that I can create a function and use 'e.target.value' with event listeners. I'm doing this in nextjs.
Why:
Because I want to store each value in to an array and localstorage before eventually displaying the data that was stored in the array as like a favorites item.
Is there a way of doing this ?
Information:
data.items has over 30+ objects -
"items": [
{
"id": 119603782,
"node_id": "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
"name": "react-contextual",
"full_name": "drcmda/react-contextual",
"private": false,
"owner": {
"login": "drcmda",
"id": 2223602,
}
{
"id": 119603782,
"node_id": "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
"name": "react-contextual",
"full_name": "drcmda/react-contextual",
"private": false,
"owner": {
"login": "drcmda",
"id": 2223602,
}
So far my data has been sorted and mapped like so.
{data.items
.sort(function (a, b) {
return b.stargazers_count - a.stargazers_count && new Date (b.created_at) - new Date(a.created_at)
})
.map((d) => (
<div onClick={checkId} key={d.id} className=" border-white p-5">
<h1 className="text-2xl font-bold">Repo name: {d.name}</h1>
An example using index as unique for displaying purpose.
const data = {
items: [
{
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
},
{
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
},
],
};
const items = data.items.map((item, idx) => ({ ...item, idx }));
// new item
const newItem = {
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
};
function addNewItems(items, newItem) {
newItem.idx = items.length;
items.push(newItem);
return items;
}
console.log(addNewItems(items, newItem));
If you want add a VALUE key to each object you can do the following:
const [isLoading, setIsLoading] = useState(false),
[items, setItems] = useState([]);
useEffect(() => {
(async () => {
setIsLoading(true);
try {
//you can replace axios with fetch
const res = await axios('https://your-api'),
// clone deep is from lodash => you can use the spread (...) operator if you want
clonedItems = cloneDeep(res.data.items);
clonedItems.forEach((el) => {
// add whatever you want in the (value)
el.value = 'required value';
});
//sort items based on the required key
clonedItems.sort((a, b) => {
//replace name with your key
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
});
//check the modified items
console.log(clonedItems);
setItems(clonedItems);
} catch (err) {
//replace it with your error handler code
console.log(err);
} finally {
setIsLoading(false);
}
})();
}, []);
Notes:
You should sort your elements before storing it in the state
You can replace axios with fetch
you can use the spread operator (...) in place of cloneDeep from lodash

Filter the data array by onChange an input value

In the code below, I am trying to run onChange={this.handleChange} with react js.I would like to obtain the items by filtering them based on what is written on Input,I tried the following :
<input value={this.state.name} onChange={this.handleChange}/>
handleChange= evt =>
this.setState(
{
name: evt.target.value.toLowerCase()
},
() => {
.
.
.
}
)
Firstly there is an input and the its function that return the value of the input.
const data=[
{ "info": [{ "name": "ali" }, { "name": "amir" }, { "name": "maya" }] },
{ "info": [{ "name": "eli" }, { "name": "mary" }] },
{ "info": [{ "name": "ali" }] },
{
"info": [{ "name": "emila" }, { "name": "alex" }, { "name": "sosan" }]
}
]
data = data .filter(item => {
if (this.renderName(item).some((r) => {
r.includes(name)
}
)) return item;
})
renderName(element){
let elementAdd = []
for (let i = 1; i < element.info.length; i++) {
elementAdd.push(element.info[i].name.toLowerCase())
}
return elementAdd
}
And I want to filter the data array based on input value, but it does not work!
Edit:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{ id: 1, info: [{ name: "ali" }, { name: "amir" }, { name: "maya" }] },
{ id: 2, info: [{ name: "eli" }, { name: "mary" }] },
{ id: 3, info: [{ name: "mary" }] },
{
id: 4,
info: [{ name: "emila" }, { name: "alex" }, { name: "sosan" }],
},
],
name: "",
};
}
reorganiseLibrary = () => {
const { name } = this.state;
let library = data;
if (name !== "") {
library = library.filter((item) => {
if (
this.renderName(item).some((r) => {
name.includes(r);
})
)
return item;
});
}
};
renderName(element) {
let elementAdd = [];
for (let i = 1; i < element.info.length; i++) {
elementAdd.push(element.info[i].name.toLowerCase());
}
return elementAdd;
}
handleChange = (evt) =>
this.setState(
{
name: evt.target.value.toLowerCase(),
},
() => {
this.reorganiseLibrary();
}
);
renderLibrary = () => {
const { library } = this.state;
if (!library || (library && library.length === 0)) {
return "";
}
return library.map((item) => <div className="item">{item.id}</div>);
};
render() {
return (
<div>
<input value={this.state.name} onChange={this.handleChange} />
{this.renderLibrary()}
</div>
);
}
}
ReactDOM.render(<App></App>, document.getElementById("app"));
There are many issues in your code and I will only discuss the critical points.
reorganiseLibrary method
data not extracted from props
handleChange method
wrong use of setState. No second parameter as far as I know.
renderName method
you only get name property but you expect an object in renderLibrary method
Here is a solution that I can think of.
state = {
data: [],
name: "",
library: [] // use this to show latest filtered data
}
function onChange(event) {
const { data} = this.state;
this.setState(
{
name: event.target.value.toLowerCase()
});
let filteredResult = [];
for(var index = 0; index < data.length; index++) {
var filteredValue = data[index].info.filter(item => item.name.includes(event.target.value));
if(filteredValue.length != 0)
filteredResult.push(filteredValue);
}
if(filteredResult.length != 0) // remove this if you want to reset the display in your UI
setState({library : filteredResult});
}
renderLibrary = () => {
const { library } = this.state;
if (library.length > 0)) {
return library.foreach(item => (<div className="item">{item.id}</div>)); // modify the onChange filter if you want the outer object
};

filter object by two nested values

I'm facing a problem with filter method. On my page there's an input to search matches by team names. Filter value is being stored to React state. Matches object looks like this:
[
{
"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": [
{
[...]
}
]
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": [
{
[...]
}
]
},
"localDate": "2020-01-01",
"localTime": "00:00:00",
"referee": null,
"commentator1": null,
"commentator2": null,
"streamer": null,
"stage": {
"id": 2,
"name": "GROUPSTAGE"
},
"onLive": true,
"finished": false
},
]
I tried tons of methods to filter matches by team name, for example:
let criteria = {
teamBlue: {
name: this.state.filter
},
teamRed: {
name: this.state.filter
}
};
let filteredMatches = this.state.matches.filter(function(item) {
for (let key in criteria) {
if (item[key] === undefined || item[key] !== criteria[key])
return false;
}
return true;
});
console.log(filteredMatches);
but none of them worked.
Is there any way to filter these matches so when I type "blue" into my input, it will show all matches where team name contains "blue"?
Thanks in advance!
Try updating the condition to:
if (!item[key] || item[key].name !== criteria[key].name)
let filteredMatches = this.state.matches.filter(function(item) {
let flag = true;
for (let key in criteria) {
// update this to
if (!item[key] || item[key].name !== criteria[key].name)
flag = false;
}
return flag;
});
The name property is missing :
if (key in item && item[key].name !== criteria[key].name)
You're comparing objects with === which will return false. You either need to use a deep comparison method from a library, or implement it yourself like below:
const matches = [ {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": []
},
}, {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRead",
"playerList": []
},
}]
const criteria = {
teamBlue: {
name: 'nameForTeamBlue',
},
teamRed: {
name: 'nameForTeamRed',
}
}
const filteredMatches = matches.filter((item) => {
const allCriteriaMatched = Object.entries(criteria)
.every(([key, value]) => {
const matched = Object.entries(value).every(([criteriaKey, criteriaValue]) => {
const itemValue = item[key][criteriaKey]
const matched = itemValue == criteriaValue
if (!matched) console.log('Item %s does not matched criteria %s. Item\'s value is %s, but criteria value is %s', item[key]['id'], criteriaKey, itemValue, criteriaValue, criteriaValue)
return matched
})
if (!matched) return false
return true
}, {})
return allCriteriaMatched
})
console.log(filteredMatches);
Basically, you just need to go 1 level deeper :D if your criteria can have multiple nested objects, then there's no point doing it manually. You can try to map criteria to run against matches so that you don't use === on objects, but only primitives.

How to delete nested state object in immutable-js inside reducer?

I want to delete an entity from data (which is List inside object someData). I am using fromJS of immutable js in my reducer to keep the state immutable
I tried using updateIn, deleteIn, update, removeIn and whatever I could find on immutable-js. But it didn't work for me. Most probably I am using these functions the wrong way.
import { fromJS, updateIn } from 'immutable';
import * as type from './constants';
export const initialState = fromJS({
someData: [],
loading: true,
});
function someReducer(state = initialState, action) {
switch (action.type) {
case type.DELETE_SINGLE_ENTITY:
updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
return state;
default:
return state;
}
}
export default someReducer;
//example someData
/*
{
date: "",
data: [
{
"id": "1",
"machine_type": "xyz",
"created_time": "2019-06-18T10:36:60Z",
...
},
{
"id": "22",
"machine_type": "abc",
"created_time": "2019-06-20T10:36:60Z",
...
},
{
"id": "2",
"machine_type": "kjhkh",
"created_time": "2019-06-11T12:36:60Z",
...
}
]
}
*/
I want to delete an entity matching with the id passed in action.
Before deleting the output of state.get('someData') is in the above example. My expected output (when action.id is 2) when I type state.get should be:
{
date: "",
data: [
{
"id": "1",
"machine_type": "xyz",
"created_time": "2019-06-18T10:36:60Z",
...
},
{
"id": "22",
"machine_type": "abc",
"created_time": "2019-06-20T10:36:60Z",
...
}
]
}
Finally! Got it!
this:
return updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
instead of this:
updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
return state;
Previously I thought updateIn would update the state itself but it doesn't it returns the updated object. So just returning the updateIn would be fine.
You can do it using the filter function
const sampleData = [{id: 1},{id: 2},{id: 3}]
const idToRemove = 2;
const updatedData = sampleData.filter(el => {
return el.id !== idToRemove
})
console.log(updatedData);

Categories

Resources