Lodash Grab key from a nested property - lodash

I've looked at a number of SO posts and gotten myself confused as to the _.findKey function of loDash.
I have working code, in that it returns the correct value, but have an unwanted side-effect that I need rid of.
Example data;
responderRooms = {
MasterVal: {
status: Value1,
time: Value2,
msg: Value3,
responders: {
4471230123456: {}
}
}
}
I know what the 4471230123456 value is and am attempting to learn the MasterVal key.
Typically, `_.findKey' would allow me to specify the array, path and a value to look for. For example,
console.log( _.findKey(responderRooms, 'time', 'Value2') // Returns MasterVal
However when the value to be matched is part of nested array, I'm trying:
var responderIndex = _.findKey(responderRooms, function(o) {
return o.responders.tel = 4471230123456;
});
Which does return the correct key, but adds a further 'tel = NewSentTo' to the ResponderVal array. Resulting in:
responderRooms = {
MasterVal: {
status: Value1,
time: Value2,
msg: Value3,
responders: {
4471230123456: {},
tel: 4471230123456 // << This shouldn't be here!
}
}
}
How can I find the Masterkey value, using the key of a nested object as criteria, without adding an additional property?
responders is created with:
objPath = MasterVal + '.responders.' + data.tel
_.set(responderRooms, objPath, {
name: data.name,
type: data.type,
msgStatus: data.msgStatus,
location: data.location,
jobStatus: data.jobStatus
});
Thanks,
Nick

Use _.has() to check if the key exists in the responders object:
const responderRooms = {
DemoKey1: {},
DemoKey2: {},
MasterVal: {
status: 1,
time: 2,
msg: 3,
responders: {
4471230123456: {}
}
}
}
const responderKey = _.findKey(responderRooms, o =>
_.has(o.responders, 4471230123456)
)
console.log(responderKey)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Related

Nuxt Store - CanĀ“t get all values in commit

In Vuex i try to send some values to a store, like this.
getStorage(
{ commit },
name: String | undefined = undefined,
objectA: ObjectA | undefined = undefined,
objectB: ObjectB | undefined = undefined
) {
if (selectedItinerary) {
commit('setA', objectA, objectB)
} else if (name) {
commit('setB', name)
}
},
The problem: Only get name value, objectA and objectB return undefinied.
So: Somebody say what's wrong? Maybe can only send one param?
UPDATE 1
Calling dispatch
this.$store.dispatch('business/getLocalStorage', {
name: this.name,
})
Store
getLocalStorage({ commit }, item) {
if (item.name) {
commit('setLocalStorageItinerary', item.name)
}
}
setLocalStorageItinerary(state, { name }: { name: string }) {
if (name) {
const itinerary = new LocalStorage()
itinerary.name = name
state.itinerary = itinerary
}
},
Assuming getStorage is an action, which receive the Vuex context as the first argument and a payload as the second, just wrap your values up inside an object before passing it as the payload.
eg.
somewhere in your app...
let payload = {
name: someValue,
objectA: someObject,
objectB: someOtherObject
}
this.$store.dispatch('getStorage', payload)
actions.js
getStorage({ commit }, payload) {
if (selectedItinerary) {
commit('setA', payload.objectA, payload.objectB)
} else if (payload.name) {
commit('setB', payload.name)
}
}
It isn't clear from your example where selectedItinerary is defined, but I think you get the gist from the above.

Detox get length of element

Hi i'm using detox and i would like to know how can I get the number of matches to
one element(length).
For example "card" match three times, how can I get the three.
const z = await element(by.id("card"))
https://github.com/wix/Detox/blob/master/docs/APIRef.Expect.md
https://github.com/wix/Detox/blob/master/docs/APIRef.Matchers.md
They don't support it in the API /:
z output:
Element {
_invocationManager: InvocationManager {
executionHandler: Client {
isConnected: true,
configuration: [Object],
ws: [AsyncWebSocket],
slowInvocationStatusHandler: null,
slowInvocationTimeout: undefined,
successfulTestRun: true,
pandingAppCrash: undefined
}
},
matcher: Matcher { predicate: { type: 'id', value: 'card' } }
}
A workaround could be
async function getMatchesLength(elID) {
let index = 0;
try {
while (true) {
await expect(element(by.id(elID)).atIndex(index)).toExist();
index++;
}
} catch (error) {
console.log('find ', index, 'matches');
}
return index;
}
then you can use
const length = await getMatchesLength('card');
jestExpect(length).toBe(3);
Here is my solution in typescript:
async function elementCount(matcher: Detox.NativeMatcher) {
const attributes = await element(matcher).getAttributes();
// If the query matches multiple elements, the attributes of all matched elements is returned as an array of objects under the elements key.
https://wix.github.io/Detox/docs/api/actions-on-element/#getattributes
if ("elements" in attributes) {
return attributes.elements.length;
} else {
return 1;
}
}
Then you can use it like this:
const jestExpect = require("expect");
jestExpect(await elementCount(by.id("some-id"))).toBe(2);

Strapi graphql mutation Syntax Error: Unterminated string

I always get Syntax Error: Unterminated string when I try to update my database using javascript strapi sdk. this.chapter.content is a html string generated by ckeditor. How can I escape this string to update my database using graphql?
async updateChapter() {
const q = `
mutation {
updateChapter(input: {
where: {
id: "${this.$route.params.chapterId}"
},
data: {
content: "${this.chapter.content.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/(?:\r\n|\r|\n)/g, '\n')}"
title: "${this.chapter.title}"
}
}) {
chapter{
title
id
content
}
}
}
`;
const res = await strapi.request("post", "/graphql", {
data: {
query: q
}
});
this.chapter = res.data.chapter;
}
Technically you could use block string notation to get around this issue. However, you really should supply dynamic input values using variables instead of string interpolation. This way you can easily provide any of sort of values (strings, numbers, objects, etc.) and GraphQL will parse them accordingly -- including strings with line breaks.
const query = `
mutation MyMutation ($chapterId: ID!, $content: String!, $title: String!) {
updateChapter(input: {
where: {
id: $chapterId
},
data: {
content: $content
title: $title
}
}) {
chapter{
title
id
content
}
}
}
`
const variables = {
chapterId: '...',
content: '...',
title: '...',
}
const res = await strapi.request("post", "/graphql", {
data: {
query,
variables,
},
})
Note that $chapterId may need to be of the type String! instead if that's what's called for in the schema. Since variables can also be input object types, instead of providing 3 different variables, you could also provide a single variable to be passed to the input argument instead:
const query = `
mutation MyMutation ($input: SomeInputObjectTypeHere!) {
updateChapter(input: $input) {
chapter{
title
id
content
}
}
}
`
const variables = {
input: {
where: {
id: '...',
},
data: {
content: '...',
title: '...',
},
},
}
Again, just replace SomeInputObjectTypeHere with the appropriate type in your schema.
Another solution maybe help
Code with issue: For example mainReason and actionTaken fields are text inputs and data contains some white spaces. This action give error: Unterminated string
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason: "${ticketData.mainReason}"
actionTaken: "${ticketData.actionTaken}"
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}
Fix this problem with JSON.stringify method
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason:${JSON.stringify(ticketData.mainReason)}
actionTaken:${JSON.stringify(ticketData.actionTaken)}
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}

Vue Router: how to cast params as integers instead of strings?

When I enter a URL using the browser field, the params are cast as strings, rather than an integer, e.g. /user/1 returns {id: "1"}. However, when when using this.$route.push({}), the params are, correctly, cast as integers {id: 1}.
Is this behavior intended? If not, how do I fix it?
You have to handle casting any params values yourself. In the route object define a props function. Here is an example:
{
path: '/user/:userId',
component: UserProfile,
props: (route) => {
/**
* This would preserve the other route.params object properties overriding only
* `userId` in case it exists with its integer equivalent, or otherwise with
* undefined.
*/
return { ...route.params, ...{ userId: Number.parseInt(route.params.userId, 10) || undefined }
}
}
link to vue router docs this is under Function mode
I'm probably late to the party, but this is my take on this. I wrote a function that returns a function that casts route params values to the props with same name with the given type.
function paramsToPropsCaster(mapping) {
return function(route) {
let nameType = Object.entries(mapping); // [[param1, Number], [param2, String]]
let nameRouteParam = nameType.map(([name, fn]) => [name, fn(route.params[name])]); // [[param1, 1], [param2, "hello"]]
let props = Object.fromEntries(nameRouteParam); // {param1: 1, param2: "hello"}
return props;
}
}
And then, in your route definition:
{
path: '/projects/:param1/editor/:param2',
component: ProjectEditor,
name: 'project-editor',
props: paramsToPropsCaster({'param1': Number, 'param2': String}),
}
This is just a hint on what you can do to solve the problem asked here, don't use this verbatim!
You can use an array in props to support both types
props: {
type:[Number,String],
required:true
}
Seems like Vue Router doesn't provide a shortcut for this, so I've come up with my own. The castParams function below generates a props function that has the specified type casting built in. I've added casting for integers and booleans but you can easily extend this for whatever other types you want to cast to.
// casts should be an object where the keys are params that might appear in the route, and the values specify how to cast the parameters
const castParams = (casts) => {
return (route) => {
const props = {};
for (var key in route.params) {
const rawValue = route.params[key];
const cast = casts[key];
if (rawValue == null) {
// Don't attempt to cast null or undefined values
props[key] = rawValue;
} else if (cast == null) {
// No cast specified for this parameter
props[key] = rawValue;
} else if (cast == 'integer') {
// Try to cast this parameter as an integer
const castValue = Number.parseInt(rawValue, 10);
props[key] = isNaN(castValue) ? rawValue : castValue;
} else if (cast == 'boolean') {
// Try to cast this parameter as a boolean
if (rawValue === 'true' || rawValue === '1') {
props[key] = true;
} else if (rawValue === 'false' || rawValue === '0') {
props[key] = false;
} else {
props[key] = rawValue;
}
} else if (typeof(cast) == 'function') {
// Use the supplied function to cast this param
props[key] = cast(rawValue);
} else {
console.log("Unexpected route param cast", cast);
props[key] = rawValue;
}
}
return props;
};
};
Then you can use it in your route definitions, eg:
{
path: '/contact/:contactId',
component: 'contact-details-page',
props: castParams({contactId: 'integer'}),
},
I do prefer Rodener Dajes answer, and handle type casting and validation within the component instead of in the route definition:
props: {
id: {
type: [Number, String],
default: 0
},
},
The reason is that it will allow me to define the route much simpler and readable:
{
path: '/job/:id',
name: 'Job',
component: InvoiceJobDetail,
props: true
}
Many of these solutions seem unnecessary complex to me.
Here's what I did in my project - note that route params ending in ID or the param id itself, are automatically converted to Number, so in my case I just had to set props: typedProps(), in nearly all of my routes.
/**
* Casts props into proper data types.
* Props ending in 'ID' and the prop 'id' are cast to Number automatically.
* To cast other props or override the defaults, pass a mapping like this:
* #example
* // Truthy values like 'true', 'yes', 'on' and '1' are converted to Boolean(true)
* {
* path: '/:isNice/:age/:hatSize',
* name: 'foo route',
* props: typedProps({ isNice: Boolean, age: Number, hatSize: Number}),
* },
* #param {Object} mapping
* #returns
*/
const typedProps = (mapping) => {
if (!mapping) {
mapping = {}
}
return (route) => {
let props = {}
for (let [prop, value] of Object.entries(route.params)) {
if (prop in mapping) {
if (mapping[prop] === Boolean) {
value = ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
} else {
value = mapping[prop](value)
}
} else if (prop === 'id' || prop.endsWith('ID')) {
value = Number(value)
}
props[prop] = value
}
return props
}
}
This could use some error handling in case a type coercion fails, but I'll leave that as an exercise for the reader :)
Based on the excellent answer from #pongi: https://stackoverflow.com/a/63897213 I came up with a new package: https://www.npmjs.com/package/vue-router-parse-props. It's written in typescript and has types. Please let me know, what you think.
npm i vue-router-parse-props
// src/router/index.ts
import propsParser from 'vue-router-parse-props'
import { parse } from 'date-fns'
const router = new Router({
base: process.env.BASE_URL,
mode: useHistory ? "history" : "hash",
routes: [
{
path: ':day/:userId',
name: 'UserProfile',
component: () => import('#/components/UserProfile.vue'),
props: paramsToPropsCaster({
userId: Number,
day: (val: string): Date => parse(val, 'yyyy-MM-dd', new Date()),
searchId: {
type: id,
routeKey: "query.q"
}
})
}
]
});

How to chain get and map the result with lodash?

I've got a list I'm trying to pull an object from using _.get but following that selection I need to loop over the object to create a new property. So far I've been successful using a combination of _.get and _.map as shown below but I'm hoping I can use _.chain in some way.
var selected = _.get(results, selectedId);
return _.map([selected], result => {
var reviews = result.reviews.map(review => {
var reviewed = review.userId === authenticatedUserId;
return _.extend({}, review, {reviewed: reviewed});
});
return _.extend({}, result, {reviews: reviews});
})[0];
Is it possible to do a transform like this using something other than map (as map required me to break this up/ creating an array with a solo item inside it). Thank you in advance!
I can see that you're creating unnecessary map() calls, you can simply reduce all those work into something like this:
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
The defaults() method is similar to extend() except once a property is set, additional values of the same property are ignored.
var selectedId = 1;
var authenticatedUserId = 1;
var results = {
1: [
{ userId: 1, text: 'hello' },
{ userId: 2, text: 'hey' },
{ userId: 1, text: 'world?' },
{ userId: 2, text: 'nah' },
]
};
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
document.body.innerHTML = '<pre>' + JSON.stringify(output, 0, 4) + '</pre>';
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>