Vue computed array appends values instead of rewriting - javascript

I have an array called contactDetails that keeps a list of different contacts points (e.g. phone number, social media handle, etc) of the user. The available contact platforms are predefined in a list. I have a computed value that keeps track of which platforms the user has not added as a contact detail yet. The computed array is used in a select field for the user to choose from when creating a new contact detail.
new Vue({
el: '#app',
data() {
return {
platforms: [
{
text: 'WhatsApp',
value: 1,
},
{
text: 'Call',
value: 2,
},
{
text: 'Email',
value: 3,
},
{
text: 'LinkedIn',
value: 4,
},
{
text: 'TikTok',
value: 5,
},
{
text: 'Twitter',
value: 6,
},
],
contactDetails: [],
},
onAddContactDetails() {
var selectedPlatform = this.platforms.find(obj => {
return obj.value == this.platformId
})
var newContact = {
platform: selectedPlatform.value,
platformName: selectedPlatform.text,
channel: this.channel,
priority: this.contactPriorityId
}
this.contactDetails.push(newContact)
this.updatePriority(newContact, this.contactDetails)
this.platformId = null
this.contactPriorityId = null
this.channel = null
this.platformList = null;
this.isAddContact = false;
},
computed: {
platformList() {
var list = [];
if (this.contactDetails.length == 0) {
return this.platforms;
} else {
list = this.platforms;
for (var i = 0; i < this.contactDetails.length; i++) {
var id = this.contactDetails[i].platform;
for (var j = 0; j < this.platforms.length; j++) {
if (this.platforms[j].value == id) {
list.splice(j, 1)
}
}
}
}
return list;
},
This is how the dropdown looks like before adding a new contact detail.
However, my computed property updates, but instead of refreshing the list, the new list is appended onto the existing options, thus causing duplication.
The original list + the new list of contact details, which is supposed to be (full list - contacts that the user has already added)
I would like to know how to fix this, and if there is a better way of setting the options available left for the user in the dropdown menu. Thanks!

You are mutating this.platforms in the computed property; you should clone it first if you are going to mutate it:
list = this.platforms.slice()
I'm not sure what's causing the duplication though. You are only ever pushing to contactDetails and removing from platforms.
Your computed property can be simplified quite a bit:
computed: {
platformList() {
// Filter platforms
return this.platforms.filter(platform =>
// Include this platform if there is no contact detail using that platform
!this.contactDetails.some(contact =>
contact.platform === platform.value
)
)
}
}

Related

Asynchronously and recursively crawl links Javascript

I am making a Blog using Notion as a content management system. There is an unofficial API provided by notion-api-js, with a function getPagesByIndexId(pageId) that returns a page's content, its subpages' contents, and, its parent's contents. So, an array of objects is returned, looking like:
[
{ moreStuff: ...,
Attributes: { slug: "home page slug", id: "home page id", moreStuff... },
},
{ moreStuff: ..., Attributes: { slug: "parent to homepage", id: "homepage's parent id", moreStuff: ... }
{ moreStuff: ..., Attributes: { slug: "sub page slug 0", id: "sub page id 0", moreStuff: ... } },
{ moreStuff: ..., Attributes: { slug: "sub page slug 1", id: "sub page id 1", moreStuff: ... } },
];
I want to build a tree that is created by recursively looping through the given id and the ids that getPagesByIndexId(given id) return to extract all slugs and ids. The function stops recursing when getPagesByIndexId(id) returns objects with ids already crawled through.
I use a crawledIdsList array to keep track of ids already crawled through, fetchPage is the same as getPagesByIndex, and I use flatmap to ignore empty []s passed by from the map function. Thanks in advance! To run this locally on node, the dependency required is npm i notion-api-js
The tree structure of the page I provided the ID with (I provided the id to the "Dev" page in homePageId) looks like:
My current code follows. It hits the "end" and returns successfully, but it is returning many pages a lot more than once.
const Notion = require("notion-api-js").default;
const token_v2 = "543f8f8529f361ab34596f5be9bc972b96ab8d8dc9e6e41546c05751b51a18a6c7d40b689d80794babae3a91aeb5dd5e47c34edb724cc356ceceacf3a8061158bfab92e68b7614516a0699295990"
const notion = new Notion({
token: token_v2,
});
const fetchPage = (id) => {
return notion.getPagesByIndexId(id);
};
const homePageId = "3be663ea-90ce-4c45-b04e-41161b992dda"
var crawledIdsList = [];
buildTree(tree={}, homePageId).then(tree => {console.log(tree)})
function buildTree(tree, id) {
return fetchPage(id).then((pages) => {
tree.subpages = [];
tree.slug = pages[0].Attributes.slug;
tree.id = id;
crawledIdsList.push(id);
return Promise.all(
pages.flatMap((page) => {
var currentCrawlId = page.Attributes.id;
if (crawledIdsList.indexOf(currentCrawlId) === -1) {
// executes code block if currentCrawlId is not used in fetchPage(id) yet
crawledIdsList.push(currentCrawlId);
return buildTree({}, currentCrawlId).then((futureData) => {
tree.subpages.push(futureData);
return tree;
});
} else {
if (crawledIdsList.indexOf(id) >= 0) {
return [];
}
return tree; // end case. futureData passed to earlier calls is tree, which looks like {subpages: [], slug: someSlug, id: someId}
}
})
)
});
}

Bootstrap-vue multiselect data binding: infinite loop

I'm trying to setup a multi select control from bootstrap-vue and bind it to a JSON object. The problem is that I need a computed value to get my json data format in a int array for the multiselect selected values and vice versa. Using such a computed property means that I change date while rendering which leads to an infinite loop.
Currently I created a computed property which has a getter which transforms the JSON object array in a integer array as well as a setter which does the opposite. In my example code the JSON object only contains the id, but in my production code there are a lot of other fields inside a "company".
<template>
<b-form>
<b-form-select
:id="`input-companies`"
v-model="companiesSelected"
multiple
:select-size="4"
:options="availableCompanies"
></b-form-select>
</b-form>
</template>
<script>
const availableCompanies = [
{ value: 1, text: 'company1' },
{ value: 2, text: 'company2' },
{ value: 3, text: 'company3' },
{ value: 4, text: 'company4' }
]
export default {
data () {
return {
employee: { id: 1, name: 'test', companies: [ { id: 1 }, { id: 2 } ] },
availableCompanies: availableCompanies
}
},
computed: {
companiesSelected: {
get () {
if (this.employee.companies == null) {
return []
}
return this.employee.companies.map(company => { return company.id } )
},
set (newValue) {
if (newValue == null) {
this.employee.companies = []
} else {
this.employee.companies = newValue.map(companyId => { return { id: companyId } })
}
}
}
}
}
</script>
The setting of this.employee.companies leads to a infinite loop. I don't really know how to avoid this. Does anyone know how to overcome this issue?
I basically split your computed set into the #change event and it seems to be working.
The #change event should only fire from user interactivity and should therefor cause the loop.
https://codepen.io/Hiws/pen/agVyNG?editors=1010
I'm not sure if that's enough for you, as i didn't take the extra fields on a company into consideration when writing the example above.

How can I set a particular userId inside my users object on click

I am mocking a userId which should be saved inside the users object of the reactions object when a certain icon is clicked inside my react component.
Below is a function updateUploadReaction that is supposed to do that for me. So, when the icon is clicked and a particular userId does not exist, it sets it inside the user object and adds 1, on clicking again it sets it to false and subtracts 1. So far, this is what I have, I need a guide on exactly how to do that.
reaction object
{
reactions: {
dislike: {
count: 0,
users: {},
},
like: {
count: 0,
users: {},
},
maybe: {
count: 0,
users: {},
},
},
}
function
function updateUploadReaction(id, type, uploads) {
const updatedUploads = new Map([...uploads.entries()]);
const upload = updatedUploads.get(id);
const userId = uuid();
uploads.forEach(() => {
if (//check if userId exists) {
upload.reactions[type].count += 1;
upload.reactions[type].users.(// user Id value) = true;
} else {
upload.reactions[type].count -= 1;
upload.reactions[type].users.(// user Id value) = false;
}
});
updatedUploads.set(id, upload);
return updatedUploads;
}
Any particular reason you can't do it the same way you do it with type using the [] syntax?
.users[userId] = true

Recursively adding and removing values from javascript object

So I'm building a basic facet search in React using data from an API, but I'm struggling with the adding & removing of values from the payload sent back to the server. The payload object can contain Arrays, Objects & Strings. So say I have a payload structure like the following:
payload = {
search_query: ""
topic: [],
genre: [],
rating: "",
type: {}
}
Using deepmerge I'm able to pass multiple values back to the API which is working fine, so an example payload would be...
payload = {
search_query: "Luther"
topic: ["9832748273", "4823794872394"],
genre: ["3827487483", "3287483274873"],
rating: "18",
type: {
args: {
when: "today",
min: 15
},
name: "spot"
}
}
So far so good, I get the expected results back. Now I have a toggle on the facet to remove it from the payload, which sends back the value to a function to remove from the payload. For example:
Clear Search, value to remove = "Luther"
Toggle of topic, value to remove = "9832748273"
Toggle of genre, value to remove = "3827487483"
Toggle of rating, value to remove = "18"
Toggle of type, value to remove = { args: { when: "today", min: 15}, name: "spot"}
Search & Rating would return empty strings, topic & genre would remove items from the arrays and type would return an empty object.
This works for removing the array values but feels dirty and I want a clean way to handle all types!
const removeObjValue = (obj, filterValue) => {
Object.entries(obj).forEach(([key, value]) => {
Object.entries(value).forEach(([subKey, subValue]) => {
if(subvalue === filterValue) {
if(Array.isArray(value)) {
const index = value.indexOf(subvalue);
if (index > -1) {
value.splice(index, 1);
}
}
}
});
});
return obj;
}
I just use delete keyword to remove object attributes, like this
if (payload[key]) {
if (payload[key] instanceof Array) {
var idx = payload[key].indexOf(value);
if (idx > -1) payload[key].splice(idx, 1);
} else {
delete payload[key];
}
}
code example

Set Enum Array based of another Enum array in the same Model - Mongoose

I am trying to build my listings model in a way where it restricts users to only select a subcategory if it exists in the parent array list. I can do this when I am building the API end point fine but I wanted to see if its possible to do this within the Model itself.
here is an example:
If(a user selects parent category household from the parent enum category array)
.then(set ENUM array based of the parent category)
Code Reference:
Model:
var categories = require(settings.PROJECT_DIR + 'controllers/data/categories');
var ListingSchema = mongoose.Schema({
data: {
category: {
parent: {
type: String,
enum: categories.parent(),
required: true
},
sub: {
type: String,
enum: categories.sub("household"), // should be this.data.category.parent instead of hard coded value
required: true
}
},
}
}
The Categories Array:
module.exports.categories = [
{ "household" : ["repair", "carpentry", "housecleaning", "electrical", "handymen", "movers", "plumbing", "gardening/pool"] },
{ "automotive" : ["autobody", "mechanics", "towing"] },
{ "creative" : ["art", "cooking", "film", "graphic_design", "marketing", "music", "writing"] },
{ "tech" : ["tech", "repair", "web_design", "web_development"] },
{ "events" : ["artist", "florist", "musician", "photography", "planning", "venue"] },
{ "legal" : ["accounting", "advising", "investments", "consulting", "real_estate"] },
{ "health" : ["beauty", "fitness", "nutrition", "therapeutic"] },
{ "pets" : ["grooming", "sitter", "trainer", "walker"] }
];
// #returns parent categories
module.exports.parent = function() {
var self = this;
var parentCategories = [];
for(var i in self.categories) {
parentCategories.push( Object.keys(self.categories[i])[0] );
}
return parentCategories;
}
// #returns sub categories based of parent input
module.exports.sub = function(parent) {
var self = this;
var subCategories = [];
for(var i in self.categories) {
if(self.categories[i][parent]) {
return self.categories[i][parent];
}
}
}
It is not possible in the model.
mongoose.Schema() methods creates a Schema object based on the parameter supplied to it. And the created object is stored in ListingSchema. Since the object is created the first time mongoose.Schema() method gets executed, you cannot modify the values dynamically after that. Which means the value accepted by enum cannot be changed once it's set.
The best approach is to add all the values in a single array then set it as the value of enum and do the sub category restriction somewhere else.

Categories