VueJS: How to pass props while redirecting - javascript

I have two single-file components each with a named route. Setup.vue is a basic form that collect and forwards some data to Timer.vue which takes some props. Is there a way to push to a route giving it the props without passing them as url attributes?
Setup.vue
<script>
export default {
...
methods: {
startTimer() {
this.$router.push({
name: 'timer',
params: {
development: this.development,
inversion: this.inversion,
stop: this.stop,
fix: this.fix,
wash: this.wash
}
})
}
...
}
</script>
Timer.vue
<script>
export default {
name: 'timer',
...
props: {
development: { type: Number, required: true },
inversion: { type: Number, required: true },
stop: { type: Number, required: true },
fix: { type: Number, required: true },
wash: { type: Number, required: true }
}
router.js
{
// I want to avoid having to do this route, instead just /timer
path: '/timer/:development/:inversion/:stop/:fix/:wash',
name: 'timer',
component: Timer,
props: true
}

Yes, you can do it and the props came in the var below:
this.$route.params
But every time you reload the page the params that are not in the URL will be lost, so this case just work when changing the route inside the app without reload.
When I have a similar problem I use query variables instead of params to solved the problem, you can use this way or make a child routes tree to organize your props.

This may help -
this.$router.push({
name: "timer",
params: { fix: { type: 1, required: true } }
});
Invoke this code post form submission. However, if someone refreshes the timer page, the route params data will be gone and you will have to handle this scenario with some other way. If the data can be retrieved from an api, it will be better if you make an api call in created method of timer page and load the data in case of refresh.

I'll add another option for the sake of completeness. Henrique's answer above is a much simpler way to achieve what I initially wanted to do, which is to route and pass props to the component without url level route variables.
Using the store as part of vuex, we could save variables in a globally accessible object and access them later in different components.
store.js
export default new Vuex.Store({
state: {
development: null,
inversion: null,
stop: null,
fix: null,
wash: null
}
Setup.vue
export default {
data() {
return {
development: null,
inversion: null,
stop: null,
fix: null,
wash: null,
store: this.$root.$store // local pointer to the global store, for conciseness
}
},
methods: {
startTimer() {
// vuex uses a transactional history model, so we commit changes to it
this.store.commit('development', this.development * 60)
this.store.commit('inversion', this.inversion * 60)
this.store.commit('stop', this.stop * 60)
this.store.commit('fix', this.fix * 60)
this.store.commit('wash', this.wash * 60)
this.$router.push({
name: 'timer'
})
},
Timer.vue
export default {
name: 'timer',
data() {
return {
state: this.$root.$store.state
}
},
computed: {
// map local to state
development() {
return this.state.development
},
stop() {
return this.state.stop
}
...

Related

Changing Jest mock value between tests

I have a utility file that uses the following implementation with a vuex store:
// example.js
import store from '#/store';
[...]
export default function exampleUtil(value) {
const user = store.state.user.current;
[...]
}
In my test, I found that I can successfully mock the value of user in the following two ways:
Manual mock
// store/__mocks__/index.js
export default {
state: {
user: {
current: {
roles: [],
isAdmin: false,
},
},
},
};
or
Mock function
// example.spec.js
jest.mock('#/store', () => ({
state: {
user: {
current: {
roles: [],
isAdmin: false,
},
},
},
}));
The issue that I'm running into is that I want to be able to change the value of current between tests, such as changing isAdmin to true or updating the array for roles.
What is the best way to do this using Jest mocks?
It turns out that the value of a mock can be changed inside a test directly after importing the mocked file.
Using the mock function example from above:
// example.spec.js
import store from '#/store'; // <-- add this
jest.mock('#/store', () => ({
state: {
user: {
current: {
roles: [],
isAdmin: false,
},
},
},
}));
it('should do things', () => {
store.state.user.current.roles = ['example', 'another']; // <-- change mock value here
[...]
});

Vue, is there a way to pass data between routes without URL params?

I am looking how to pass data secretly between two separate components (not parent and child) without using URL params in my Vue2 app. This doesn't mean I am passing secrets but rather I just dont want the user to see it (only for UI considerations).
I know Vue has Props but they are meant for passing data between parent and child component.
In my case, my URL will change but I don't want to pass data via visible params.
Someone claimed to use props without URL params here but I haven't been able to reproduce a working solution (getting undefined each time).
I also checked out these options but they are all using either URL or query params which as we know are visible.
An ugly solution would be to write the data to local storage and then read it there but this creates a lot of overhead and complexity (like what if I only want this data to be read once, etc).
Is there a more elegant solution to this problem?
Thanks!
make props: true for the destination route -- in the index.js file of router
{
path: '/home',
name: 'home',
component: taskChooser,
props: true,
}
define prop in the component e.g props: ['myprop'], - note the quotes
copy the variable you want to pass from the source route into the same name as your prop - in this case myprop
myprop = theVariableThatYouWantToPass
this.$router.replace({name:'home', params:{myprop}});
Make sure that the name of prop and variable are same - the prop is in quotes.
It's working for me.
Thanks #Mayank for pointing me in the correct direction.
Here is the correct syntax that worked for me.
Notice the props in In router index
{
path: '/componentPath',
name: 'componentName',
props: {
header: true,
content: true
},
}
In the component you are redirecting to, define the props as following:
props: {
myProperty: {
type: <DATATYPE>
},
}
Perform redirect as following:
this.$router.push({
name: 'componentName',
params: {
myProperty: <VARIABLE>
}
})
Access props with the this. convention from created or in later lifecycle event.
In this case, the variable name and property name do not have to be the same as it is a simple map. That naming convention would be a curious design choice anyway.
I haven't tested this in Vue 2, but in Vue 3, you can pass a stringified object through the props when you click on a link:
Add props: true to your routes file, for the route.
{
path: 'receipt',
name: 'receipt',
component: () => import('../pages/Receipt.vue'),
props: true,
beforeEnter(to, from, next) {
if (!to.params.receiptData) {
return next({
name: 'dashboard',
params: {
locale: from.params.locale ? from.params.locale : 'en',
},
});
}
return next();
},
},
Include your stringified object as a param for router.push().
const receiptData = {
transferType: 'default',
recipient: receiver.value.name,
referenceNumber: '#B3423424234',
amountSent: formAmount,
transferFee: 0,
};
router.push({
name: 'receipt',
params: {
receiptData: JSON.stringify(receiptData),
},
});
Declare the props as instance data in the component.
<script setup>
import { computed } from 'vue';
const props = defineProps({
receiptData: {
type: String,
required: true,
},
})
console.log('receiptData', props.receiptData);
const parsedReceiptData = computed(() => JSON.parse(props.receiptData));
</script>
I haven't tested an upper limit for size, so be careful about passing a huge object through, and you'll notice I showed a beforeEnter middleware on the route too because, if the user presses F5 to refresh the page, the props will be lost, so in my case, I redirect the user away from the page because the receipt is for one time use only.

Access to props in data

can you tell me what I do wrong? I need access to props in my data object in component.
I have defined component like this:
export default {
components: {...},
computed: {...},
props: {
userCode: {
type: String,
default: null
}
},
data: () => ({
options: {
callback: function() {
console.log(this.userCode) // prints undefined
return ...;
}
}
}),
methods: {...},
...,
}
prop value I define in router like this:
{
path: '/user/bbb',
name: 'users',
component: userView,
meta: {
requiresLoggedIn: true,
},
props: {userCode: 'XXX'}
}
When I tried in same component render this prop in html like this {{this.userCode}} so it's worked and display my passed code. How to access to prop in options data object? Thanks.
Vue.js best practices aside the reason this.userCode is undefined is because in that case the callback function defines its own this and the global this is not being used. To use the global this either use
callback: () => {}
or
callback: function() {
console.log(this.userCode) // prints undefined
return ...;
}.bind(this)
you can read more about the arrow function here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

VUE 2.0 - Unable to get props id through $router.push to details page

I have a list of Tickets which works fine (companytickets), and when clicked.. it opens up a details page (companyticket) for that specific ticket, passing the id to the component.
problem is i can't find out how to access this prop parameter in the created event, since it's not accessable through "this".
companytickets.vue :
viewTicket: function(ticket){
this.$router.push('/companyticket/' + ticket.Id)
// works : this redirects to http://localhost:8180/companyticket/3
}
companyticket.vue
export default {
name: 'CompanyTicket',
props: {
id: {
type: Number,
required: true
}
},
created() {
this.$store.dispatch('getCompanyTicket', this.id)
// ERROR : this.id is undefined...
console.log("Created here :")
}
}
route config
{ path: '/companyticket/:id', component: CompanyTicket, props: true }
Scenario
this.id is "undefined"
when using this.$route.params.id i get the correct id parameter, but in some weird way it claims to use "companytickets/2" (which is the parent page). The Correct should be companyticket/2.
Screenshot of Chrome Dev :
Use object-style or payload for passing params to actions.
Change:
this.$store.dispatch('getCompanyTicket', this.id)
To:
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
Now your files looks like this:
companyticket.vue
created() {
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
}
store.js
actions: {
getCompanyTicket({ commit }, { id }) {
console.log("ID is available now-->", id)
}
}
Vuex
Since you're using Vuex state management pattern, that would be another approach to share data between component.
It allow parent-child communication and same for child-parent (sharing data with props allow only parent-child communication). Inject store into to your root component:
const app = new Vue({
el: '#app',
// provide the store using the "store" option.
// this will inject the store instance to all child components.
store,
})
This is everything you need in your store object:
var store = new Vuex.Store({
state: {
ticketID: Number
},
mutations: {
UPDATE_TICKET_ID(state, ticketId) {
state.ticketId = ticketId;
}
},
actions: {
getCompanyTicket({ commit, state }, { id }) {
commit("UPDATE_TICKET_ID", id)
}
}
}
Also if you want to update state:
The only way to actually change state in a Vuex store is by committing
a mutation
Any property from state will be available in every component:
console.log(this.$store.state.ticketId)

Merge Vue props with default values

I have an options prop in my Vue component that has a default value.
export default {
props: {
options: {
required: false,
type: Object,
default: () => ({
someOption: false,
someOtherOption: {
a: true,
b: false,
},
}),
},
},
};
If the options object is passed as a prop to the component, the default value is replaced. For example, when passed { someOption: true }, now the options object contains only that value.
How can I pass a partial object and override the default values with the given values instead of replacing the whole object?
I've encountered a similar problem recently and used Object.assign
Here is the docs from mozilla https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
A concrete usage of your case would be something like that:
props: {
options: {
required: false,
type: Object,
default: () => ({}),
},
},
data(){
mergedOptions:{},
defaultOptions:{
someOption: false,
someOtherOption: {
a: true,
b: false,
},
}
},
mounted(){
//you will have the combined options inside mergedOptions
Object.assign(this.mergedOptions,this.defaultOptions,this.options)
}
By doing this, you will override only the properties that passed via props. Don't know if it's the most efficient way but it's very understandable and neat :)
So if you pass in as props :options={someOption:true} the merged options will be equivalent to:
{
someOption: true,
someOtherOption: {
a: true,
b: false,
},
}
EDIT: If you need your data to be reactive, you might want to have a computed.
computed: {
mergedOptions(){
return {
...this.defaultOptions,
...this.options
}
}
}
You will actually never want to modify props within components. If you do, you break one-way-dataflow of parent/child components and your code will be hard to reason about what is affecting what.
Lifted right from the Vue docs, the correct solution is to either (1) use an initial prop or (2) a computed value, so your app can be reactive and respect parent components, and you can rest easy and kick your feet up :)
Both solutions assume your template will use opts for options...
Solution 1: Use an initial prop (defaults and options):
props: ['options', 'defaults'],
data: function () {
var opts = {}
Object.assign(opts, this.defaults, this.options)
return {
opts: opts
}
}
Solution 2: Define a computed property so your component can react to prop changes:
props: ['options', 'defaults'],
computed: {
opts: function () {
let opts = {}
Object.assign(opts, this.defaults, this.options)
return opts
}
}
A quick thought experiement will show, if a parent component changes your input props, your component can properly react.

Categories

Resources