How is $route object initialized? - javascript

I tried to access this.$route on created() hook, but as I log it to the console it always returns an empty object
{path: '/', name: undefined, params: {…}, query: {…}, hash: '', …}
fullPath: "/"
hash: ""
matched: []
meta: {}
name: undefined
params: {}
path: "/"
query: {}
redirectedFrom: undefined
[[Prototype]]: Object
I guess it is because the loading is asynchronous? Then how can it locate the path from the beginning ??
I tried to do this and it worked. I wonder if anybody could explain what happens under the hood.
localhost:8080/?server=BlackHole
// index.js
{
path: '/network-error',
name: 'NetworkError',
component: NetworkError
},
{
path: '/:server',
},
// App.vue
created() {
setTimeout(() => {
// △ Request to unreachable server
if (this.$route.query.server == 'BlackHole')
{this.$router.push({ name: 'NetworkError' })}}, 1)},
Thanks in advance :)

When you'r on the /network-error route, the app is above that, so you can't say in the app where are you in my app ?
Because your application at this time created and mounted the view app and after go inside the route detected.
The good and normal practice is to fetch every route before like a middleware, inside your router.js or routes/index.js use this function:
router.beforeEach(async (to, from, next) => {
if (to.name === 'BlackHole') {
next({ name: 'NetworkError' })
}
next()
})
This maybe doesn't not exactly match your redirect because i miss some information, but i think you understand what you have to do.

The problem was because the route load is an async process, I found a very quick solution here, just add this line of config to main.js:
router.isReady().then(() => {app.mount('#app')})
https://www.youtube.com/watch?v=a6gT6qHtch8&ab_channel=VueMastery

Related

How do I route with query string params in Next.js?

I have a URL like
bar?id=foo
If I route to this via router.push('bar?id=foo') everything works fine.
But if I go direct to the route in the browser, the query string does not get passed through.
const BarComponent = ()=>{
console.log(useRouter())
}
This outputs
ServerRouter {
route: '/bar',
pathname: '/bar',
query: {},
asPath: '/bar',
basePath: '',
events: undefined,
isFallback: false,
locale: undefined,
isReady: false,
locales: undefined,
defaultLocale: undefined,
domainLocales: undefined,
isPreview: false,
isLocaleDomain: false
}
That's the output you get on the terminal from the server, and shows its default value ({}). On the client, you should see 2 console logs: first one with an empty query object (from SSR) and a second one with your query string in it (from rehydration).
useRouter() is a React Hook so it'll only run/update on the client.
If you want to have access to the query string on the server-side, you'll need to use getServerSideProps and access it through the query param from the context object.
export async function getServerSideProps({ query }) {
console.log(query) // `{ id: 'foo' }`
//...
}

React/NextJS: Dynamic Routing: TypeError: Cannot read property 'query' of null --> Warning: Did not expect server HTML to contain a <div> in <div>

I'm have a NextJS application which I've just done two things on. 1) I've upgraded to the latest version of NextJS (9.0.1), and 2) I've copied files (package.json, pages/, components/ etc.) from my project root to a new src/app/ sub-directory in my project root.
Pages which don't use dynamic routing load perfectly fine, but there's an issue when I need to use the router object from Next. My original code (which was working fine before the above two things):
import { useRouter } from "next/router";
import { mockConsts } from "../../public/mockConsts";
const router = useRouter();
const userObj = mockConsts.users.filter(
user => user.username === router.query.username
)[0];
This gives me the error: TypeError: Cannot read property 'query' of null.
If I wrap a try/catch around it:
let userObj = mockConsts.users[0];
try {
const router = useRouter();
userObj = mockConsts.users.filter(
user => user.username === router.query.username
)[0];
console.log("QUERY", router.query);
} catch (e) {
console.log("ERR", e);
}
the client and server return two different errors. The server still returns the above error (TypeError: Cannot read property 'query' of null).
The client returns (via console.log):
QUERY Object { } [username].tsx:16
Warning: Did not expect server HTML to contain a <div> in <div>. index.js:1
QUERY Object { username: "dog" } [username].tsx:16
With the try/catch I can get the code to work as intended, but only after it has displayed mockConsts.users[0] (The first "QUERY" in console log is blank). Obviously this isn't ideal.
EDIT:
I have rewritten the try/catch so the functionality is as I'd expect:
try {
router = useRouter();
console.log("ROUTER", router);
userObj = mockConsts.users.filter(
user => user.username === router!.query.username
)[0];
} catch (e) {
console.log("ERR", e);
}
ROUTER is logged twice:
ROUTER
Object { pathname: "/user/[username]", route: "/user/[username]", query: {}, asPath: "/user/[username]", components: {…}, events: {…}, push: field(), replace: field(), reload: field(), back: field()
, … }
[username].tsx:23
ROUTER
Object { pathname: "/user/[username]", route: "/user/[username]", query: {…}, asPath: "/user/dog", components: {…}, events: {…}, push: field(), replace: field(), reload: field(), back: field(), … }
[username].tsx:23
As you can see, the query attribute has values in it now, and the asPath attribute is different.

Cannot get cookie in vue-router to work. Undefined "this"

The problem is that I cannot get cookie in vue-router. When I try to do this: this.$cookie.get('token'); I get this undefined. Even though I can use this.$cookie in vue components. This is my code snippet:
Vue-Router:
This is a function to check if I get token from cookie (but it doesnt work):
function isLoggedIn (to, from, next) {
console.log(this.$cookie.get('token'));
}
And this is the route and I use that isLoggedIn in it:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: isLoggedIn()
},
Maybe someone knows how to get that cookie ? It it setted up in vue component correctly just don't know how to pass it to vue router.
EDIT
So by using Vue.cookie.get('token'); I get that next is not a function. This is the current function:
function isLoggedIn (to, from, next) {
if(Vue.cookie.get('token')) {
next({name: 'game'});
} else {
next();
}
}
Okay so when I added this function directly into route like this it worked:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: (to, from, next) => {
if(Vue.cookie.get('token')) {
next('/game');
} else {
next;
}
}
},
If you are using the vue-cookie plugin from npm then you will need to use this syntax outside of components:
function isLoggedIn (to, from, next) {
console.log(Vue.cookie.get('token'));
}
With regards to your next is not a function error, this is because you are invoking your function with no arguments, isLoggedIn(), rather than passing the function reference, isLoggedIn, which will be invoked by the router with the appropriate arguments.
Try this instead:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: isLoggedIn // No parentheses here
},
I hope this helps.
If you look at the typings, you'll see that both to and from are Route's. Here's their interface declaration:
export interface Route {
path: string;
name?: string;
hash: string;
query: Dictionary<string | (string | null)[]>;
params: Dictionary<string>;
fullPath: string;
matched: RouteRecord[];
redirectedFrom?: string;
meta?: any;
}
If look at the Definition of BeforeEnter:
beforeEnter?: NavigationGuard;
If you look at the Definition of NavigationGuard:
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: V) => any) | void) => void
) => any
So as we can see, the beforeEnter returns a closure that exposes 3 methods to us: (to, from, next).
As we can see the typings of both to and from are just Route Interfaces and next is a function that we pass arguments into, we can safely determine that the scope of this is indeed, undefined.
So you can either define it in the meta of the Route declaration and access it with this.$meta.cookies, or you can import the cookie package directly and use it with cookies.get('token') or you can augment the return type of the beforeEnter method:
beforeEnter: (to, from, next) => isLoggedIn(to, from, next, $cookies)
You still, cannot, use the in component guards as they do not expose this to you because they execute before the component has been created

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.

How to get data before Router is loaded after Auth Service using angular 2+?

I hope this is not duplicated.
I have this component to Login with a user, that calls a service and gives me a Token.
When the login is success I save the token to localStorage and I get the user Data with getUserData service and save it to a localStorage with saveUderData service.
onLoginSuccess(token: Token) {
this.authService.saveToken(token);
this.authService.getUserData().subscribe(
userData => this.onGetUserDataSuccess(userData)
);
}
onGetUserDataSuccess(userData: UserDataModel) {
this.authService.saveUserData(userData);
this.router.navigate(['/'])
}
The problem is that when I go to the main component the data is not
loaded and I have to refresh the page.
I have this in my main component.
if (localStorage.getItem('_UserToken') && localStorage.getItem('_UserData') && (!this.authService.userToken || !this.authService.userToken.access_token)) {
this.authService.saveToken(JSON.parse(localStorage.getItem('_UserToken')));
this.authService.saveUserData(JSON.parse(localStorage.getItem('_UserData')));
this.userData = this.authService.userData;
}
with this.userData I get the data.
I solved it in another component with Resolve but here I can't do it because i don't know how.
I tried this in onLoginSuccess(token: Token)
this.route.data.subscribe(
userData => console.log(userData['user'])
);
but the userData is undefined
here my routes.
const appRoutes: Routes = [
{path: '', canActivate: [AuthGuardService], data: { title: 'Inicio'}, resolve: {user: UserDataResolve},
children: [
{path: '', component: HomeComponent},
{path: '', component: MenuComponent},
]},
{path: 'login', component: LoginComponent, data: { title: 'Iniciar Sesión'} },
];
and here is my Resolve
#Injectable()
export class UserDataResolve implements Resolve<UserDataModel> {
constructor(private authService: AuthService) {}
resolve(route: ActivatedRouteSnapshot): Observable<UserDataModel> {
return this.authService.getUserData();
}
}
I hope you can understand me, my english is not the best. thanks =).
Try using a getter instead of a simple property in your component:
get userData(): string {
this.authService.userData;
}
That way any time changes are detected it will get the current userData from your service.

Categories

Resources