vue router - where to store route names? - vuejs2

I'm building a fairly large app using Vue and I have a lot of routes, names and logic behind them. I found a great question/answer about Vue Router here and it gave me a lot of ideas, but I wasn't able to find an answer for myself. If I missed something, please feel free to point me in the right direction.
Here are my issues:
Renaming hell: whenever I change a name of the route I have to go and find all the self.$router.replace({ name: 'OldRouteName' }) and change them. Maybe it doesn't sound like a big deal, but it is, especially in a big project
Global guard hell: in a way it is related to the #1 where I'm relying on the text literals to analyze from and to and call my next({ name: 'RouteName', replace: true })
There are a few other minor things on the list, but those two are big for me.
My thought was to create a global object and store route names there something like Vue.prototype.$myRoutes = {Index:'Index', Home: 'Home'} etc, but for some reason it doesn't feel right. I hope there is something better out there 🙂
Any help appreciated, thanks!

Just like you can store Vuex mutation types in constants, so too can you store route names.
For example
// router/route-names.js
export const INDEX = "Index"
export const HOME = "Home"
Now wherever you need them, you just import from this file
import { INDEX, HOME } from '#/router/route-names'
Making changes means only editing one file.
For point #2, you can store extra data in each route's meta property. For example
{
name: ROUTE_A,
path: '/route/a',
meta: {
nextRoute: { name: ROUTE_B, replace: true }
}
}
Then you can use
next(from.meta.nextRoute)

Related

React native - State won't update on navigation

I'm not sure if the title is informative enough, but I'll try explaining my need.
I have two stacks in my application. I have a floating component that appears everywhere in my app(No matter in which stack I am), that shows some items, and by clicking them it will navigate to a component that renders this item. The navigation code is:
navigation.navigate('Tabs', {
screen: 'Home',
params: { screen: 'Dish', params: { from: '', data: dish } },
})
Now the problem is, if I'm already inside the screen Dish that render this item, and then use the floating component to navigate to another item, my state isn't rerendering, and keeps its old values.
I've managed to solve this by changing the code to:
navigation.push('Dish', {from: '', data: dish})}
Which simply push the component into the stack, though it made another problem; If I'm in my second stack (not the Tab one), then this doesn't work and won't navigate me anywhere, which make sense..
I also managed to solve this by navigating to the Tab stack and then pushing my component like this:
navigation.navigate('Tabs', {screen:'Home'})
navigation.push('Dish', {from: '', data: dish})}
This works, though I'm not sure if this is good practice. I was wondering if this is the correct way of achieving what I want.. Maybe I should just make my component rerender so the state changes? I tried to include as little code as I could, if anything else is needed I'll add it..
Thanks in advance!
It's not the best solution but you can use this :
componentDidUpdate(prevProps) {
if (this.props.route.params !== prevProps.route.params) {
//What you want to do
}
Using the DidUpdate only when the params change to avoid infinite loop.
Or looking after listener.

How do I pass parameters with router.push in vue.js?

I'm working on a vue.js application. I'm trying to pass parameters in a redirect from one component to another like this:
this.$router.push({
path: '/my-path',
query: {
anArray: [...]
}
});
Once the component at my-path loads, I'm able to retrieve the parameters like this:
const theArray = this.$route.query.anArray;
The problem is that as soon as I refresh the page, the parameters are gone. When I open Chrome DevTools and put a break point where I retrieve the array from $route.query, I see this:
0: "[object Object]"
1: "[object Object]"
2: "[object Object]"
It seems obvious that it's getting this from the url which is:
http://localhost:8080/my-path?anArray=%5Bobject%20Object%5D&anArray=%5Bobject%20Object%5D&anArray=%5Bobject%20Object%5D
It doesn't seem to realize the 'object' terms in the url are just encodings of actual objects, which are available from $route.query the first time it loads.
Is there another way to pass parameters to a component using $router.push() such that the parameters persist on the page even after refreshing?
I could just try:
this.$router.push('/my-path?anArray=[...]');
...but my goal is to hide the parameters from the user (so don't show them in the url). This is another reason I'm looking for an alternate way of passing parameters (it doesn't even hide them).
I also tried:
this.$router.push({
path: '/my-path',
params: {
anArray: [...]
}
});
...but this made the parameters unavailable in the component (I wonder if this has anything to do with our global router which routes '/my-path' to the MyPath component without specifying parameters; is it wiping out the parameters?).
Thanks.
If you want to hide the parameters from the user, you must not use query. Instead, you should use parameters. Here I let you an example:
//routes.js
path: '/:data',
name: 'Home',
component: () => import('pages/YourPage.vue')
//Passing parameters
this.$router.push({
name: 'Home',
params: { data: yourData}
});
//Receiving parameters in Home component
created() {
console.log('Params: ', this.$route.params);
}
I hope this could be usefull
While params suggested #elC0mpa is a correct answer here are some alternatives depending on the use case:
localStorage/SessionStorage
Save the paramters into localStorage/SessionStorage and retrieve them in the loading sequence of your destination-page as #nachodd pointed out.
âš  It should be noted that only key value pairs in form of strings are being saved into these storages.
You will need something along the line of
localStorage.setItem(itemKey,JSON.stringify(itemValue)
to set the values and
const itemValue = JSON.parse(localStorage.getItem('itemKey')
to recive it. Also localStorage.removeItem('itemKey') for cleanup.
vuex store
You may want to consider saving your data in the store all together and access it from any vue-component.
The commands needed are this.$store.commit('pathToStore/setterForKey',value) to save something into the store and this.$store.getters[pathToStore/getterForKey'] to receive the items value.
âš  It should be noted that you need to set up the store accordingly with every state setter/mutation, getter and action. See this documentation.

Best practice to handle route names in react-navigation v5

I'm new to react navtive and wondering how to handle the route names for the react-navigation v5.
I'm starting of a company internal boilerplate project with react-navigation in version 4 and updating that to version 5. So far everything runs fine.
The problem now is, the project was using a file which had all the route names defined as so:
export const HOME_ROUTE = 'Home';
export const LOGIN_ROUTE = 'Login';
export const REGISTER_ROUTE = 'Register';
That also works in react-navigation v5.
Then I wanted to eliminate the warnings on the any type of the navigation prop. The documentation states I should add the type definition. Then I ran into the first problem.
With the route constants I would have liked to do it somehow like this:
export type RootStackParamList = {
HOME_ROUTE: undefined;
LOGIN_ROUTE: undefined;
REGISTER_ROUTE: undefined;
};
That does not work, as I cannot find a way to use constants in a type definition(Which makes sense).
I was thinking about removing the routeConstants altogether, but the disadvantage is that I do not have autcompletion on typing the route names, which might lead to hard to spot bugs. At least I think that might happen.
Is there a preferred way to handle this problem? I looked into a few other boilerplate projects but they all just used strings as the route names.
Thanks to a coworker I found the solution:
The syntax to unpack a constant is like this:
export type RootStackParamList = {
[HOME_ROUTE]: undefined;
[LOGIN_ROUTE]: undefined;
[REGISTER_ROUTE]: undefined;
};

How to update multiple dashboard components on click - Vue.js

I've found it very difficult to find help online with this issue as no examples seem to match my use case. I'm basically wanting to check if I am on the right track in my approach.I have a single page Vue app:
Each row on the right is a component. On the left are listed three data sets that each possess values for the fields in the dashboard. I want it to be so that when you click on a dataset, each field updates for that set.
So if you click on 'Beds', the title becomes 'Beds' and all the fields populate the specific data for beds.
I want to do this without having separate pages for each dataset since that would seem to defeat the point of using a reactive framework like Vue. Only the embedded components should change, not the page.
I have installed Vue Router and have explored using slots and dynamic components but it is very hard to understand.
If someone experienced in Vue could just let me know the right broad approach to this I then know what I need to look into, at the moment it is difficult to know where to start. Thank you
You can use Vuex for that purpose.
Add property to the state, dataset for example. And mutation to change it. Every component on the right side should use that this.$store.state.dataset (or through mapState) for its own purposes. So when you're selecting one of listed datasets on the left side, it will mutate dataset in store with its own data.
Something like that:
store (there are alternate version, where we can use getter, but its little bit more complicated for just an example).
import Vue from 'vue';
const store = new Vuex.Store({
state: {
dataset: {}
},
mutations: {
setDataset(state, payload) {
Vue.set(state, 'dataset', payload);
}
}
});
one of the right side component
computed: {
dataset() {
return this.$store.state.dataset;
},
keywords() {
return this.dataset.keywords;
},
volume() {
return this.dataset.volume;
}
}
left menu
template:
{{dataset.title}}
code:
data() {
return {
datasets: [{
id: 1,
title: 'Sofas',
keywords: ['foo'],
volume: 124543
}]
}
},
methods: {
changeDataset(dataset) {
this.$store.commit('setDataset', dataset);
}
}
datasets is your data which you're loading from server.
BUT You can use some global variable for that, without Vuex. Maybe Vue observable, added in 2.6.

In Vue.js, how do you prevent navigation for a subroute?

The nice thing about beforeRouteLeave is that you can prevent navigating away under certain conditions.
I have a setup that uses a subroute to render part of the page. I would like a navigation guard on the subroute to prevent switching to another one if the data is not saved.
{
path: '/customers/view',
component: ViewCustomerShell,
children: [
{path: ':id', name: 'ViewCustomer', component: ViewCustomer}
]
},
So when I visit /customers/view/12 and make a change, if they try to load /customers/view/13, I want to pop up the usual confirmation and potentially stop navigation. Since beforeRouteLeave is not called in this situation, what is the recommended approach for preventing navigation? It seems that watching $route would be too late, because then the navigation has already occurred.
Note: As mentioned above, beforeRouteLeave is not called in this situation; it doesn't work.
Note: Using onbeforeunload doesn't work because it only triggers when the entire page changes.
I have also posted the same answer here.
Dynamic route matching is specifically designed to make different paths or URLs map to the same route or component. Therefor, changing the argument does not technically count as leaving (or changing) the route, therefor beforeRouteLeave rightly does not get fired.
However, I suggest that one can make the component corresponding to the route responsible for detecting changes in the argument. Basically, whenever the argument changes, record the change then reverse it (hopefully reversal will be fast enough that it gets unnoticed by the user), then ask for confirmation. If user confirms the change, then use your record to "unreverse" the change, but if the user does not confirm, then keep things as they are (do not reverse the reverse).
I have not tested this personally and therefor I do not gurantee it to work, but hopefully it would have cleared up any confusion as to which part of the app is responsible for checking what change.
I know that this post is very old. but it was the first one I found when looking for the same problem.
I have no idea if there is a better solution nowadays but for those who are looking for a solution, I can share mine:
1. Define a global state
let isRouteChangeBlocked: boolean = false;
export function blockRouteChange(set?: boolean): boolean {
if (arguments.length == 1) {
isRouteChangeBlocked = !!set;
return isRouteChangeBlocked;
}
return isRouteChangeBlocked;
}
2. Replace the route function
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location: RawLocation) {
if (blockRouteChange()) {
if (confirm("Du hast ungespeicherte Änderungen, möchtest du fortfahren?")) {
blockRouteChange(false);
return originalPush.call(this, location) as any;
}
return;
}
return originalPush.call(this, location) as any;
};
3. Set the state
#Watch("note.text")
private noteTextChanged() {
blockRouteChange(true);
}
This does exactly what I want. If nowadays there is a better solution, let me know. You can get the full runnable example here: https://github.com/gabbersepp/dev.to-posts/tree/master/blog-posts/vuejs-avoid-routes/code/example
You could use a $route object inside your component to watch if it changes and then raise up the confirmation modal... This will get called whenever your route changes!
const Baz = {
data () {
return { saved: false }
},
template: `
<div>
<p>baz ({{ saved ? 'saved' : 'not saved' }})<p>
<button #click="saved = true">save</button>
</div>
`,
watch: {
'$route': function () {
if (this.saved || window.confirm('Not saved, are you sure you want to navigate away?')) {
// do something ...
}
}
}