Vue Router: how to cast params as integers instead of strings? - vue.js

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"
}
})
}
]
});

Related

Nuxt.js store - update object in store object - throw Error: [vuex] do not mutate vuex store state outside mutation handlers

in my Nuxt.js app
my store object is:
export const state = () => ({
curEditRP: {
attributes:{
name:"",
spouse: {
type: "", // wife/husband
name: ""
}
}
})
to update the attributes of curEditRP i wrote mutations function that called setCurEditRPAttrState:
export const mutations = {
setCurEditRPAttrState(state, payload) {
state.curEditRP.attributes[payload.attr] = payload.value;
},
}
from template i used it:
this.$store.commit("setCurEditRPAttrState", {
value: value,
attr: attributeName,
});
In a name update it works great
But in a spouse update it throws an error
Error: [vuex] do not mutate vuex store state outside mutation handlers
examples of values:
name (works great):
this.$store.commit("setCurEditRPAttrState", {
value: "Peter",
attr: "name",
});
spouse (throws an error):
this.$store.commit("setCurEditRPAttrState", {
value: { type:"wife",name:"S" },
attr: "spouse",
});
clarification: value is v-model variable
Bs"d, I find the solution.
in update object or array of object i need itarate over object properties and update each one individually
setCurEditRPAttrState(state, payload) {
if(typeof(payload.value) == 'object') {
let stateAttribute = state.curEditRP.attributes[payload.attr];
if(Array.isArray(payload.value)) {
let stateArrLen = stateAttribute.length;
let valuelen = payload.value.length;
while(stateArrLen > valuelen) {
stateAttribute.pop();
stateArrLen --;
}
for (let index = 0; index < payload.value.length; index++) {
const element = payload.value[index];
if(stateAttribute.length < index + 1) stateAttribute.push({});
Object.keys(element).forEach( key => {
Vue.set(stateAttribute[index], key, element[key])
})
}
}
else {
Object.keys(payload.value).forEach( key => {
Vue.set(stateAttribute, key, payload.value[key])
})
}
}
else {
state.curEditRP.attributes[payload.attr] = payload.value;
}
},

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);

How to pass an array values from one function to another function in vuejs?

I am trying to get the array values from
"validateBeforeSubmit" function to "saveForm" function. But I am
getting values of "undefined" in "arrlength". Please help me to solve.
This my code in vue.js
export default {
name: '',
data() {
return {}
},
ready: function() {
this.validateBeforeSubmit()
this.saveForm();
},
methods: {
validateBeforeSubmit() {
var fieldsVal = new Array();
var firstName = document.getElementById('firstName').value
var lastName = document.getElementById('lastName').value
var designation = document.getElementById('designation').value
if (firstName != "" && lastName != "" && designation != "") {
fieldsVal.push(firstName);
fieldsVal.push(lastName);
fieldsVal.push(designation);
return fieldsVal;
} else {
fieldsVal.length = 0;
return fieldsVal;
}
return fieldsVal;
},
saveForm() {
var fieldsValArray = this.validateBeforeSubmit();
var arrLength = fieldsValArray.length;
}
}
}
I can see multiple issues in your code:
1) Don't apply jQuery-like approach for getting input values. Use v-model instead. This will simplify your code
<template>
<input v-model="form.firstName" type="text"/>
</template>
<script>
export default {
data: {
form: {
firstName: '',
}
},
methods: {
validateBeforeSubmit() {
// take `firstName` directly from `data` not need for `getElementById`
const firstName = this.form.firstName;
}
},
}
</script>
2) Remove validateBeforeSubmit and saveForm from ready. Ready hook is obsolete in vue#2. And also it makes no sense. It's better to call it on form #submit.
3) It's better to create array using [] syntax instead of new Array()
Why never use new Array in Javascript
4) Always provide name for your component for easier debug
export default {
name: 'ValidationForm',
}
5) I don't know where was an issue but it works. Check this link below. I have updated your code. Try to submit form and check the console:
https://codesandbox.io/s/w6jl619qr5?expanddevtools=1&module=%2Fsrc%2Fcomponents%2FForm.vue

Vuex Getters Method Style How to Return Function

According to the Vuex documentation, you can pass a payload to a getter method as long as the method returns a function.
https://vuex.vuejs.org/en/getters.html
I'm unclear on how I can format a function that returns a function.
In my case, I'd like to pass a product object to a getter method and use the product.price data along with data in the state to return a calculated price.
Here's a stripped down version of the approach I have currently:
const store = new Vuex.Store({
state: {
product: {
price: 12.99,
},
colors_front: 1,
colors_back: 0,
},
getters: {
printPrice: (state) => (product) => {
return (state, product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
}
},
}
}
When I try to access this getter in my component, I'm receiving the code of the function as a text string.
<template>
<div>{{ printPrice(product) }}</div>
</template>
export default {
computed: {
...mapState(['product']),
...mapGetters(['printPrice']),
}
}
Can anyone help me understand getters that return functions better? Is there a more appropriate way to do this?
I figured since I'm not actually mutating the state data, this method belonged better as a getter than a mutator, but I'm open to all suggestions.
Thanks!
Problem is that your getter is returning a function that also returns a function, so, when Vuex runs the function, it returns another one which seems to be cast to string (maybe by the template parser?).
Just make sure to return one single function with the expected output by changing this:
printPrice: (state) => (product) => {
return (state, product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
}
},
to this:
printPrice: (state) => (product) => {
var colors_front = state.print_product.colors_front,
colors_back = state.print_product.colors_back;
print_price = parseFloat(product.price) + parseFloat(colors_front * 2.25) + parseFloat(colors_back * 2.25);
return parseFloat(print_price).toFixed(2);
},
That way we removed the wrapping function in the first level returning function.

Filter data using lodash

How can filter the data with some inner attribute value.
updated_id='1234';
var result= _.map(self.list, function (item) {
// return only item.User_Info.id=updated_id
});
You can use the lodash#matchesProperty variant of lodash#filter to filter out objects that you need using the path of the property. The variant is in the 3rd example of the lodash#filter documentation.
var result = _.filter(self.list, ['User_Info.id', updated_id]);
var self = {
list: [
{ User_Info: { id: '4321' } },
{ User_Info: { id: '4321' } },
{ User_Info: { id: '1234' } },
{ User_Info: { id: '3214' } },
{ User_Info: { id: '2143' } }
]
};
var updated_id = '1234';
var result = _.filter(self.list, ['User_Info.id', updated_id]);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
lodash has a filter method:
const updated_id='1234';
const result= _.filter(self.list, item => item.User_Info.id === updated_id);
Use lodash _.filter method:
_.filter(collection, [predicate=_.identity])
Iterates over elements of collection, returning an array of all elements predicate returns truthy for. The predicate is invoked with three arguments: (value, index|key, collection).
with predicate as custom function
_.filter(myArr, function(o) {
return o.name == 'john';
});
with predicate as part of filtered object (the _.matches iteratee shorthand)
_.filter(myArr, {name: 'john'});
with predicate as [key, value] array (the _.matchesProperty iteratee shorthand.)
_.filter(myArr, ['name', 'John']);