Pass an object as param to router.push (vue-router#4.05) - vue.js

Problem
router.push({name:"Order", params: {obj: {}})
fails to push obj: {}, instead the route receives obj: '[object Object]'
Situation
I have a route setup like this
{
path: '/Order',
name: 'Order',
component: () => import("../views/Order.vue"),
props: route => {
return route.params.obj // '[object Object]'
},
}
this results in props not being defined in Order.vue
Expected Result
{
...
props: route => {
return route.params.obj // '{}'
},
}
Based on this answer objects work in older versions
What I've tested
I've used jest to inspect the arguments passed to router.push and they appear as they should: {name:"Order", params: {obj: {}}
Any Ideas?

Passing objects to params was never supported in Vue-router
It sort of worked in Router v3 with $router.push - the target component received the object. BUT as soon as user started using browser navigation (Back button) OR copy/pasting URL's, this solution was broken (you can try it here - just click the button and then use back and forward controls of the frame)
As a rule of thumb - if you want to pass anything to the target route/component, such data must be defined as parameters in the route definition so it can be included directly in the URL. Alternatives are passing data in a store, query params (objects needs to be serialized with JSON.stringify), or using the history state.
This was true for Vue-router v3 and is still for Vue-router v4
Note
Just to explain alternatives. By "store" I do not mean just Vuex. I understand Vuex can be overkill for some small applications. I personally prefer Pinia over existing Vuex. And you can create global state solution yourself with composition API very easily...

Related

Vue composition api - composable with global state that uses component props

In my app there is a composable called useLocalization. It provides translated strings based on the user's language preferences, and is used throughout the app.
The problem is, that useLocalization can be configured, and accepts the following arguments, but I don't know what the best way of passing these arguments from the root component to the composable:
interface ILocalizationProps {
currentLocale: Ref<string>
fallbackLocale: Ref<string>
locales: Ref<Partial<ILocale>[]>
}
The root component that uses useLocalization accepts the same arguments as props, so the consuming App can configure/override the language used.
const DEFAULT_LANG = 'en'
export const withLocalizationProps = () => ({
currentLocale: { type: String, default: null },
fallbackLocale: { type: String, default: DEFAULT_LANG },
locales: { type: Array as () => Partial<ILocale>[], default: () => [] },
})
How can I initialize my composable with the props passed to the root component? Here's what I tried/issues I found:
useLocalization(props) doesn't work here, as the props are not available deeper in the component tree.
Using provide/inject I can use props in the provide part, and get the correctly configured version with inject, but this prevents my from using useLocalization in the root component, as the injection is not available.
Use a hacky solution such as in vee-validate / injectWithSelf. Even then, The signature of the composable would be useLocalization(props?: IProps), and my root component has to be the first one to call this function with the props.
Use a helper such as createInjectionState, but it's the same problem as 2.
Is there a best way to solve this? Composable that don't depend component state, such as useMouse work great, but (globally) configurable composables cause the afore mentioned issues :(
In that case, it'd be better to use provide/inject than using props.
Here's the reference.
https://vuejs.org/guide/components/provide-inject.html

How can I send and get with this.$router.push the params between two components

I have two components.
The first one:
This component has this one:
this.$router.push({path: '/dte', params: { id: 1 }});
I am sending and redirecting to /dte
And I have a second one which it has in the created part of the vuejs code this:
export default {
created() {
alert(this.$router.params.id);
}
}
I wonder:
How can I check if id is defined?
I wonder why does it display this error - Error in created hook: "TypeError: Cannot read property 'id' of undefined"?
Thanks
Be careful, there are two globals objects injected by vue-router:
$router: The vue-router instance
$route: The current route state described in an object
Here, you have to check for this.$route.params.id to get the current route parameter.

Data passing using props in router is not working

I am trying to send data from one vue component to another by using props in router. but it is not working. whenever i try to log the props it outputs undefined. code is given below
From where data is sending
Where receiving
in index.js. router setting
None of the code you've posted matches up.
Firstly, the console logging should be just console.log(this.myprops). The point of using props is that you don't need to reference the router itself, e.g. via $router.
Next problem, you're mixing path and params. That isn't allows. See https://router.vuejs.org/guide/essentials/navigation.html. params are for named routes.
I imagine what you're aiming for is something like this:
self.$router.replace({ name: 'DashboardPatient', params: { myprops: authUser.email } })
with router config:
{
path: '/patient',
component: Dash,
children: [
{
path: ':myprops', // <--- Adding myprops to the URL
name: 'DashboardPatient',
component: DashboardPatient,
props: true,
meta: { requiresAuth: true }
}
]
}
Keep in mind that routing is all about building and parsing the URL. So the value of myprops needs to be in the URL somewhere. In my example it comes at the end, so you'll get /patient/user#example.com as the URL. If it weren't in the URL then there'd be no way for the router to populate the prop if the user hit that page directly (or refreshed the page).
To hit the same route using a path instead of a name it'd be something like this:
self.$router.replace({ path: `patient/${encodeURIComponent(authUser.email)}` })
or even just:
self.$router.replace(`patient/${encodeURIComponent(authUser.email)}`)
Personally I'd go with the named route so that the encoding is handled automatically.
If you don't want to put the data in the URL then routing is not the appropriate way to pass it along. You'd need to use an alternative, such as putting it in the Vuex store.

How to handle navigation when removing the current page's Vuex record?

I have a ClientManagePage where I display client information and allow for the removal of the displayed client.
The vue-router route configuration for that page looks like this:
{
path: '/client/:id/manage',
name: 'client',
component: ClientManagePage,
props: ({ params }) => ({ id: params.id }),
}
The client entities are stored in a vuex store. ClientManagePage gets its client entity from the store using the id prop and displays various properties of the client and a "remove" button.
The remove button listener is (inside a mapActions):
async removeClientClicked(dispatch) {
// Wait for the action to complete before navigating to the client list
// because otherwise the ClientListPage might fetch the client list before
// this client is actually deleted on the backend and display it again.
await dispatch('removeClientAction', this.id);
this.$router.push({ name: 'clientList' });
},
The vuex action that removes a client is:
async function removeClientAction({ commit }, id) {
// Remove the client from the store first (optimistic removal)
commit('removeClient', id);
// Actually remove the client on the backend
await api.remove('clients', id);
// Moving "commit('removeClient', id);" here still produces the warning mentioned below
}
My problem is how to handle navigating to the other route when removing a client. The current code produces warnings in development mode such as:
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
found in
---> <ClientManagePage> at src/pages/ClientManagePage.vue
<Root>
This is of course caused by the reactivity system kicking in and trying to update the content of the page with the now-deleted vuex client entity. This happens before the removeClientAction is completed therefore the navigation to the ClientList page.
I've come up with some possible solutions to this, but they are not very appealing:
Have a v-if="client" at the top of the ClientManagePage that hides everything while the client does not exist in the store.
Use the computed property client in ClientManagePage to return a default "dummy" client that contains the required properties for the page. The page will still flash with "fake" content while the action is underway though.
Navigate to "clientList" right after (or even before) dispatching removeClientAction. This causes the clientList to display the removed client briefly while the action completes which is not good.
Are there other solutions to this seemingly common problem of navigating away when deleting the underlying vuex entity displayed on the current page?
I ended up doing a big v-if at the top of the ClientManagePage that hides everything while the client does not exist in the store. It's not pretty, but it works. An improvement could be to display a "please wait, operation in progress" in v-else.
One option is to externalize the deletion of the record. There are a number of ways to do that, but the simplest for me was to create a new route, /records/delete/:id, and place a route guard on that route that triggers the removal. Then redirect to the records list where you wanted to go in the first place. Something along the lines of:
import store from "wherever/your/store/is";
const routes = [{
path: "/records/delete/:id",
name: "deleteRecord",
props: true,
beforeEnter: (to, from, next) => {
store.dispatch("DELETE_RECORD", to.params.id).then(() => console.log("record deleted!"));
next({name: "some/path/you/wanted/to/go/to"});
}
}, ...];

Navigate with Vue router-link changing only query parameters, not path

In Vue.js, I'm using <router-link> to navigate, as such:
<router-link :to="{ path: '/', query: { q: item.id, lang: lang } }">{{item.name}}</router-link>
This does not update the path, only the query string. Although the resulting URL is formatted correctly, it does not trigger navigation in Vue, apparently because the path has not changed, only the query string.
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object.
How can I make Vue perform the navigation, even though only the query parameters have changed?
You have most probably solved this already, so this answer could be useful for others with this problem.
Documentation link - In-Component Guards - beforeRouteUpdate
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object
This means you doing it all correctly since you are seeing the new parameters in the component guard, So next, what you need to do is to do as prescribed below in docs and example
Fetching After Navigation
So all you now have to do is fetch your new data using these new parameters
beforeRouteUpdate (to, from, next) {
this.post = null
// replace `getItem` with your data fetching util / API wrapper
getItem(to.params.q, to.params.lang, (err, post) => {
this.post = post
next()
})
}