How to augment search function to include array within objects - arrays

Given data in this format:
// projects.json
{
businessName: "",
address: "",
city: "",
reference: "",
contacts: [
{
name: ""
phone: ""
},
{
name: ""
phone: ""
}
],
}
... and a search function (btw, this is a Vue app) which iterates the 'project' objects of the json:
export default {
computed: {
filteredProjects: function() {
const searchTerm = this.search.toLowerCase();
if (!searchTerm) {
return false;
}
return this.projects.filter((project) => {
return (project.businessName.toLowerCase().match(searchTerm)) ||
(project.reference.toLowerCase().match(searchTerm));
});
}
} // computed
} // export default
... how can I augment this function to include in the search the 'contacts' array within each 'project' object, e.g.:
return this.projects.filter((project) => {
return (project.businessName.toLowerCase().match(searchTerm)) ||
(project.reference.toLowerCase().match(searchTerm)) ||
// PSEUDO-CODE (searching contact name doesn't throw error but returns 100% of the data):
(project.contacts.filter((el) => {
el.name.toLowerCase().match(searchTerm);
}))
});
Thanks in advance for any help or suggestions,
Whiskey T.

Two things:
filter returns an array, so its value will always be true; you need to check the length of it to get a false value when it's empty
you need a return in your project.contacts.filter arrow function

Related

Insert list data over the iteration(map)

Here I am trying to modify my data over the iteration and send some result to API call.
The API Call receives a request with a structured data format which is
{ list: [{ id: "1", name: "Hello" }, ... ] }
Somehow I managed to call the API with single data ( const params in my current code, it only accepts single data).
But now it has to be done with multiple data something like this:
{ list: [{ id: "1", name: "Hello" }, { id: "22", name: "Ed" }, { id: "36", name: "Jason" } ... ] }
Here is my current code
const [table, setTalbe] = useState(..); // assume, we have some table data here
const processNow = () => {
let id = 0;
let name = '';
// if table length is greater than 1, we go for the loop.
if (table.length >= 1) {
table.map(data => {
id = data.userId;
name = data.userName;
});
//insert table data to params, here I want to add whole table data into "list"
//the final result of this list should be something like this
//ex ) list: [{ id: '123', name: 'Josh' }, { id: '125', name: 'Sue' }, { id: '2222', name: 'Paker' } ...],
// but how??
const params: any = {
list: [
{
id: id,
name: name
},
],
};
//send PUT reqeust with params
axios
.put(
'/api/v1/tosent',
params,
)
.then(res => {
console.log('The response', res);
})
.catch(err => {
console.log('The error: ', err);
});
}
};
but I'm stuck with it, please help me to finish this code to work properly.
need your kind advice.
Array.prototype.map returns a new array with the function you pass applied to every element. You should study the MDN documentation on map to understand its use.
Your current code does nothing with the map return value:
table.map(data => {
id = data.userId;
name = data.userName;
});
You probably assumed .map would mutate the data, as in change it in place. Instead, the whole operation returns a new array.
It looks like you want to do:
const list = table.map(data => {
return {
id: data.userId,
name: data.userName
}
});
This is applying a function to every element in the array that will map each element to a new object, matching your question, with an id and name key. Then it looks like you want to pass the returned value of map (which we named list above) to your call:
const params: any = {
list: list
};

add dynamic empty objects to an array React

I have the following code, which basically adds empty objects to an array.
handleAddNewRow = () => {
this.setState({
rowData: [
{ MEMBER: "", ALIAS: "", STATUS: "" },
...this.state.rowData
]
})
}
Lets say, I am passing an integer value to the function handleAddNewRow and then it dynamically adds the number of empty objects to the array based on the integer value, How is it possible?
You can look at my function:
handleAddNewRow = (number) => {
this.setState({
rowData: [
...this.state.rowData,
...(new Array(number).fill({ MEMBER: "", ALIAS: "", STATUS: "" }))
]
});
}
in the following code i wrote code in simple condition
change it on your own
const array = [{name: '', family: ''}]
function a(num, arr) {
let temp = [...arr, {name: '', family: ''}]
if (num - 1 > 0) {
temp = a(num - 1, temp)
}
return temp
}
const b = a(4, array)
console.log(b)

In React, how do I name a field of my form that is part of an array?

I'm building a React 16.13.0 application. In my form, I want to submit data (an address) as part of an array, so I set up my state like so ...
constructor(props) {
super(props);
this.state = {
countries: [],
provinces: [],
errors: [],
newCoop: {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
country: FormContainer.DEFAULT_COUNTRY,
}],
enabled: true,
email: '',
phone: '',
web_site: ''
},
I then created these functions for managing changes to the input fields ...
  handleInput(e) {
    let self=this
    let value = e.target.value;
    let name = e.target.name;
    this.setValue(self.state.newCoop,name,value)
  }
  setValue = (obj,is, value) => {
       if (typeof is == 'string')
         return this.setValue(obj,is.split('.'), value);
       else if (is.length === 1 && value!==undefined) { 
         return this.setState({obj: obj[is[0]] = value});
       } else if (is.length === 0)
         return obj;
       else
         return this.setValue(obj[is[0]],is.slice(1), value);
  }
...
                <Input inputType={'text'}
                   title= {'Street'} 
                   name= {'addresses[0].formatted'}
                   value={this.state.newCoop.addresses[0].formatted} 
                   placeholder = {'Enter address street'}
                   handleChange = {this.handleInput}
                   errors = {this.state.errors} 
                  /> {/* Address street of the cooperative */}
The Input.jsx file looks like the below ...
const Input = (props) => {
    return (  
  <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
            isInvalid={props.errors && Boolean(props.errors[props.name])}
            type={props.type}
            id={props.name}
            name={props.name}
            value={props.value}
            placeholder={props.placeholder}
            onChange={props.handleChange}
          />
      {props.errors && props.errors[props.name] && (
          <FormControl.Feedback type="invalid">
                 {props.errors[props.name].map((error, index) => (
                     <div key={`field-error-${props.name}-${index}`} className="fieldError">{error}</div>
                 ))} 
          </FormControl.Feedback>
      )}
  </div>
    )
}
export default Input;
However, when I attempt to change the value, I get the below error. I'm not sure what else I need to be doing to name my component such that I can successfully change it's value. I would prefer not to change the data structure in my constructor, but I'm willing to if that's what it takes.
TypeError: Cannot set property 'formatted' of undefined
FormContainer.setValue
src/containers/FormContainer.jsx:127
124 | if (typeof is == 'string')
125 | return this.setValue(obj,is.split('.'), value);
126 | else if (is.length === 1 && value!==undefined) {
> 127 | return this.setState({obj: obj[is[0]] = value});
| ^
128 | } else if (is.length === 0)
129 | return obj;
130 | else
ISSUE:
Cannot set property 'formatted' of undefined
// Reason : because you can't access obj["addresses[0]"]["formatted"]
// Solution : it should look something like obj["addresses"][0]["formatted"]
Because you are splitting up string by ., so a result you are getting
[
"addresses[0]",
"formatted"
]
Now that you have successfully splitted up the string ,
You are trying to get object by name, specifically obj["addresses[0]"], But you can't access the object index like this,
It will give you undefined, so as a result, you are getting the above error. you can check that exact error by running below code snippet,
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(".")
console.log("obj['addresses[0]'] ===>" , obj[names[0]])
console.log("obj['addresses[0]']['formatted'] ===>" , obj[names[0]][names[1]])
SOLUTION :
So now question is if not obj["addresses[0]"] this then what, the solution is obj["addresses"]["0"],
So you have 2 options :
First : change this addresses[0].formatted to addresses.0.formatted
Second : you need to split the sting with .split(/[\[\].]+/)
I would prefer second option as this addresses[0].formatted looks real form name, and this is how it should look like, you can check that in below code snippet also.
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(/[\[\].]+/)
console.log("obj['addresses'] ==>" , obj[names[0]])
console.log("obj['addresses']['0'] ==>" , obj[names[0]][names[1]])
console.log("obj['addresses']['0']['formatted'] ==>" , obj[names[0]][names[1]][names[2]])
NOTE :
Now, once you solved the issue, real issue come up in the picture, obj: obj[is[0]] = value, here obj is object so this will throw error , and also your setValue function is limited to that functionality only, it should be generic
handleInput = e => {
let name = e.target.name;
let value = e.target.value;
const keys = name.split(/[\[\].]+/);
this.setState(this.updateValue(this.state, keys, value));
};
// I've created a recursive function such that it will create a
// copy of nested object so that it won't mutate state directly
// obj : your state
// name : input name
// value : value that you want to update
updateValue = (obj, name, value, index = 0) => {
if (name.length - 1 > index) {
const isArray = Array.isArray(obj[name[index]]);
obj[name[index]] = this.updateValue(
isArray ? [...obj[name[index]]] : { ...obj[name[index]] },
name,
value,
index + 1
);
} else {
obj = { ...obj, [name[index]]: value };
}
return obj;
};
WORKING DEMO :
Your code is quite confusing, that's part of your problem to begin with, the other problem with your code is that it is not good practice to have nested objects in react's state. You can learn more by reading this answer in this other question.
Here is an example of what you could do with your code to set the state, however, notice that this is a bad way of solving the issue:
handleInput(e) {
let value = e.target.value;
this.setState(prevState =>{
...prevState,
newCoop: {
...prevState.newCoop
addresses: [
{
...prevState.newCoop[0].addresses
formatted: value
}
]
}
})
}

Custom Validate an object Push to an array

I am using something similar to the following schema.
By visiting the Item page I can add related items to the Item's Related Items array field.
I would like to custom validate the object I am pushing to the Item's Related Items field, to test if a similar object exists in the array already - so that I do not get a duplicate.
In my code below, the custom validation does not fire. I expect this may be because custom validation cannot be applied to a type: [object], and should be applied to the properties of the object - but then I am unable to test the object as a whole.
const ItemsSchema = new SimpleSchema({
name: {
type: String,
label: 'Name',
},
related: {
type: [Object],
label: 'Related Items',
optional:true,
custom: function () {
let queryData = { docId: this.docId, related: this.value }
if (Meteor.isClient && this.isSet) {
Meteor.call("relatedObjectIsUniqueForThisItem", queryData,
function (error, result) {
if(!result){
console.log("not unique");
return "Invalid";
}
else{
return true;
}
});
}
}
},
'related.$.name':{
type: String,
label:'Name',
},
'related.$.code':{
type:String,
label:'Code',
min:5,
},
});
I figured out the way to handle this.
The custom validation should not be on the [object], but rather one of the properties of the object - in this case 'source' or 'code'.
Inside one of the object properties you can call this.siblingField(otherField); But it means you have to rebuild the object.
In my case :-
const ItemsSchema = new SimpleSchema({
name: {
type: String,
label: 'Name',
},
related: {
type: [Object],
label: 'Related Items',
optional:true,
},
'related.$.name':{
type: String,
label:'Name',
custom: function () {
//---------------------------
//this is the important bit
//---------------------------
let queryData = {
docId: this.docId,
related: {
name:this.value,
code:this.siblingField('code').value,
}
}
//---------------------------
//end of important bit
//---------------------------
if (Meteor.isClient && this.isSet) {
Meteor.call("relatedObjectIsUniqueForThisItem", queryData,
function (error, result) {
if(!result){
console.log("not unique");
return "Invalid";
}
else{
return true;
}
});
}
}
},
'related.$.code':{
type:String,
label:'Code',
min:5,
},
});

Push to array within subdocument in mongoose

I'd like to push to an array that's within a subdocument in Mongoose/MongoDB.
Here is the schema:
var UsersSchema = new mongoose.Schema({
user: String,
stream: String,
author: String,
tags: Array,
thumb: Number,
added: Number
})
var ContentSchema = new mongoose.Schema( {
title: String,
url: String,
description: String,
text: String,
users: [ UsersSchema ]
})
I'd like to push an array into the UserSchema.tags array for a specific users sub-document.
I have tried this and several variations: https://stackoverflow.com/a/19901207/2183008
By default, my front-end Angular app is sending the tags as an array of objects. So it's
[ { 'text': TAG_STRING_HERE } ]
or
[ { 'text': TAG_STRING_HERE }, { 'text': TAG2_STRING_HERE } ]
But I've also tried just using and array of strings, which I'm fine doing if objects are a problem for some reason.
I have tried this:
var tags = req.body.tags,
contentId = mongoose.Types.ObjectId( req.body.id )
Content.update(
{ 'users._id': contentId },
{ $push: { 'users.tags': { $each: tags } } }
).exec()
.then( function ( result ) {
console.log( result )
resolve( result )
}, function ( error ) {
if ( error ) return reject( error )
})
Which gives me this error:
{ [MongoError: cannot use the part (users of users.tags) to traverse the element ({users: [ { user: "54f6688c55407c0300b883f2", added: 1428080209443.0, stream: "watch", _id: ObjectId('551ec65125927f36cf4c04e9'), tags: [] }, { user: "54f6688c55407c0300b883f2", added: 1428080222696.0, stream: "listen", _id: ObjectId('551ec65e25927f36cf4c04ea'), tags: [] } ]})]
name: 'MongoError',
code: 16837,
err: 'cannot use the part (users of users.tags) to traverse the element ({users: [ { user: "54f6688c55407c0300b883f2", added: 1428080209443.0, stream: "watch", _id: ObjectId(\'551ec65125927f36cf4c04e9\'), tags: [] }, { user: "54f6688c55407c0300b883f2", added: 1428080222696.0, stream: "listen", _id: ObjectId(\'551ec65e25927f36cf4c04ea\'), tags: [] } ]})' }
The solution is below. Note this is using Q and Express. The part of note is the 'users.$.tags. I thought I had tried this but I guess not! I also used $pushAll instead, but $each might also work. My tags is always an array.
var tags = req.body.tags,
contentId = mongoose.Types.ObjectId( req.body.id )
console.log( tags )
Content.update(
{ 'users._id': contentId },
{ $pushAll: { 'users.$.tags': tags } }
).exec()
.then( function ( result ) {
console.log( result )
resolve( result )
}, function ( error ) {
if ( error ) return reject( error )
})

Resources