Vue RouterLink links to undefined after a history state is loaded - vue.js

I use vue navigation with Components. On a single site I use History States to navigate between specific states of a tabview. But whenever I use return to load a previous state, all the Links in the RouterLink are changed like this: "www.foo-bar.com/tasks" to "www.foo-bar.comundefined" even mith a missing slash. What am I doing wrong with the state handling?
My RouterLink:
<RouterLink :to="{ name: 'tasks' }" class="navbar-item is-brand">
<i
aria-hidden="true"
class="iconify sidebar-svg"
data-icon="feather:align-justify"
></i>
</RouterLink>
Where I set the history:
history.pushState({
part: 'tasks'
}, document.title, `${window.location.pathname}?part=tasks`);
Where I load it:
window.onpopstate = function() {
let urlParams = new URLSearchParams(window.location.search);
part.value = urlParams.has('part') ? urlParams.get('part') : "tasks"
}
The issue is removed after I set a new history state. Do I have to set a new history state after reloading, or is this a bad workaround?

Related

Vue.js passing a variable through a modal

I am trying to pass a variable from a Parent (page) component to a Child (modal) component. After reading a few examples, this works fine. The variable in question is brought in from another component as a route param. If i refresh the page, the variable is lost and can no longer be passed to the child. My question is, is the best way to persist this using the store, or is it possible to persist another way if the user refreshed? Any help would be appreciated
Parent
<b-container>
<Modal v-show="displayModal" #close="closeModal">
<template v-slot:body>
<ExpressionCreate v-show="displayModal" :lender-id="lenderId" #close="closeModal"/>
</template>
</Modal>
<div class="card" style="width: 100%;">
<div class="card-header">
<h5>{{this.lenderName}}</h5>
<b-alert :show="this.loading" variant="info">Loading...</b-alert>
</div>
<b-btn block variant="success" #click="showCreateLenderModal">Add Expression</b-btn>
....
created () {
this.lenderId = this.$route.params.lenderId
...
navigate () {
router.go(-1)
},
showCreateLenderModal () {
this.displayModal = true
},
toggleDisplayModal (isShow) {
this.displayModal = isShow
},
async closeModal () {
this.displayModal = false
}
Child
<label>lender id:</label>{{this.lenderId}}
...
props: {
lenderId: {
type: Number
}
},
You can use VueSession to persist.
Simply persist your lender_id with
this.$session.set('lender_id', this.lender_id)
and you can get it later as
saved_lender = this.$session.get('lender_id')
if (saved_lender) {
// handle saved case here
}
You can use query params in URI by doing:
$route.push('/', { query: { param: 3123 } })
This will generate the URL /?param=3123 and this will work after refresh. Or, you can use vuex and vuex-localstorage plugin, that let you persist data in the browser, and you can close the browser and open again and data will be restored.
In order for the state of application to persist, I recommend that you use vuex to manage the state and use a library that abstracts the persisting to vue in a clean way. One popular library is vuex-persist. https://www.npmjs.com/package/vuex-persist
If you dont want to use a store, VueSession, a package linked here should do the trick

Vue.js router-link params expected to be defined but getting browser error "missing param for named route <route_name>: expected <param> to be defined

Hi I'm trying to use a Vue.js router-link to pass a parameter dynamically into a component. I'm getting a browser error that says that the parameter is not defined, even though the router URL is being displayed correctly, with the parameter. The component is also rendered, but the data does not seem to be accessible in the component as a property or data element.
Here is my table with router-link:
<sui-table-row v-for="(player, i) in allPlayers" :key="i">
<sui-table-cell>{{player.league}}{{i+1}}</sui-table-cell>
<sui-table-cell>{{ player.player1 }}&{{player.player2}}</sui-table-cell>
<sui-table-cell selectable v-for="(week, j) in allDates" :key="j">
<router-link :to="{ name: 'addWeek', params: {team1: player._id } }">
{{player.schedule[j]}}
</router-link>
</sui-table-cell>
</sui-table-row>
Routes.js
import AddWeek from '#/views/admin/addWeek.vue';
const routes = [{
path: '/ncl-schedule/addWeek/:team1',
name: 'addWeek',
component: AddWeek
}]
export default routes
Two issues I'm having here:
1: Chrome is throwing the following error:
[vue-router] missing param for named route "addWeek": Expected "team1" to be defined
This is odd because I get the team id passed as the param on the route, and it brings me to the AddWeek component as I'd expect.
http://localhost:8080/ncl-schedule/addWeek/5e6bc31785aa8e5ab4575d80
2: I can't get the data as a property or data element in the component, however.
The error indicates that player _id isn't ready when the component is first rendered, maybe it's async data. Try a v-if on your link:
<router-link v-if="player && player._id" :to="{ name: 'addWeek', params: {team1: player._id } }">
Params are accessed at this.$route.params in the target component. You can automatically convert those to props if you prefer, by defining your route with the props: true option:
const routes = [{
path: '/ncl-schedule/addWeek/:team1',
name: 'addWeek',
component: AddWeek,
props: true
}]
You need to also create the team1 prop in addWeek.
Click Schedule Link Here to View Example
With regard to the initial Chrome error, Dan was correct that the data was async and therefore not rendered upon initial load. This is resolved now in this solution.
I ultimately wanted multiple parameters to be passed in from the component. In order to accomplish this I used "query" opposed to "params", in the "router-link". The rest of the code block is included for clarification of the parameters being passed.
Parent Component:
<sui-table-row v-for="(players, i) in allPlayers" :key="i">
<sui-table-cell>{{players.league}}{{players.teamNumber}}</sui-table-cell>
<sui-table-cell>{{ players.player1 }}&{{players.player2}}</sui-table-cell>
<sui-table-cell selectable v-for="(week, j) in allDates" :key="j">
**<router-link :to="{ name: 'addWeek', query: {selectedTeam: players, week: week.weekId, match: players.schedule[j] } }">
{{players.schedule[j]}}
</router-link>**
</sui-table-cell>
</sui-table-row>
routes.js
const routes = [{
path: '/schedule/addWeek',
props(route) {
return route.query || {}
},
name: 'addWeek',
component: AddWeek
}]
Finally within the component I wanted to access the data I retrieved them from the route within the mounted hook as follows:
Consuming Component:
async mounted() {
// this is the team in the players field who's week's matches were chosen.
this.selectedTeam = this.$route.query.selectedTeam
// teams clicked on
this.teamsPlayed = this.$route.query.match
// this is the week the matches take place - intersection of selectedTeam, date
this.schedule.weekId = this.$route.query.week
}

hidden params in router link using vuex

I have two going to the same component, but I want to update an attribute of my store.js when I click on each of these .
<router-link :to="'service'" #click="origin(2)"><a class="navigation-links "><i class="fas fa-coins" /> New service </a></router-link>
<router-link :to="'service'" #click="origin(1)"><a class="navigation-links"><i class="fas fa-coins" /> Update service </a></router-link>
The code of function origin is:
function origin(newValue) {
this.$store.commit(SET_ORIGIN, newValue)
}
But it never has been called and I don't have my store updated
I don't want to have two different url, because for the users it needs to be the same action
I have tested with the event #click.native, but it doesn't work neither
Any suggestions?
Have you tried navigating inside the method after saving your data?
<div #click="origin(2)"></div>
function origin(newValue) {
this.$store.commit(SET_ORIGIN, newValue);
this.$router.replace({ name: "service" });
}

Why in my nuxt-link doesn't reload page with same url?

If I’m on a page with the URL 'http://localhost:8080/item' and I’m clicking on the same link on this page, then the page does not reload.
I need to make that if I click on the same link, the page will reload.
My link:
<nuxt-link :to="/item">
Any insight will be welcome. Thanks!
Use key, something like:
<router-view :key="$route.params.yourCustomParam"/>
Also you can use something like:
<router-link :to="{ params: { yourCustomParam: Data.now } }" replace>link</router-link>
Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting .
I recently tried to solve a similar issue and to overcome this I used Vuex with :key (ref).
Firstly, in your store you need a state property such as:
export const state = () => ({
componentUpdates: {
item: 0,
//can add more as needed
}
})
In general, you could use only one property across the app if you prefer it that way. Just remember that later on, the key value needs to be unique - that is in the case if you used this property for two or more components within one page, for example. In this case, you could do something like this :key="$store.getters.getComponentUpdates.item+'uniqueString'"
then a getter:
export const getters = {
getComponentUpdates(state) {
return state.updateComponent;
}
}
finally a mutatation:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update]++
}
}
Now we can utilise the reactive :key wherever needed.
But first in your nuxt-link lets add an event to trigger the mutation, note the usage of #click.native to trigger the click event:
<nuxt-link #click.native="$store.commit('updateComponent', { update: 'item'})" :to="/item">
Now in the item page, for example. Let's imagine there is a component that needs to be updated. In this case we would add :key to it:
<my-item :key="$store.getters.getComponentUpdates.item" />
That is it. As you can see this solution utilises the benefits of nuxt-link but also allows us to selectively update only parts of our page that need updates (we could update the entire page this way as well if needed).
In case if you needed to trigger the logic from mounted or initial load in general, then you could use computed property and :key to your div container, right inside the <template> of your page.
Add :key to the div:
<template>
<div :key="$store.getters.getComponentUpdates.item"></div>
</template>
Create computed property:
computed: {
updateItemPage() {
//run your initial instructions here as if you were doing it in mounted then return the getter
this.initialLoadMethod()
return this.$store.getters.getComponentUpdates.item
}
}
The final touch, which is not crucial but can be implemented in order to reset the state property:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update] >= 10
? state.componentUpdates[payload.update] = 0
: state.componentUpdates[payload.update]++
}
}

vuejs - Keep Form data when browser back

I do not speak English, I'm using google translator.
I am having a problem with the back button after a form submit, I use vuejs to display content and manipulate / validate a form, clicking the submit button is redirected to another action. if I click back in the browser vuejs reloads the previous page, so I lose the data of my form, how do I keep the data of a form without using a , since it is a submit button? I believe that the tag does not suit me in this case (I tested it and it did not work).
Simply you can use keep-alive in Vue js to cache states on back action.
For more information:
https://v2.vuejs.org/v2/api/#keep-alive
keep alive support in Vue2 and also Vue3, but the syntax is different. Also you can use in Nuxt.
For better performance you should limit components to be caches by "max" and "include" props.
you can use in layout or only wrap components:
Wrap in layout:
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
Wrap components to cache:
<keep-alive>
<product-list></product-list>
</keep-alive>
Nuxt:
<template>
<div>
<Nuxt keep-alive :keep-alive-props="{ exclude: ['modal'],max:3 }" />
</div>
</template>
** Keep alive should be in the same layout(if you use keep alive in layout).
** If you destroy keep alive cache, the caching will not work anymore.
** You can control cache with "key" props. For same "key" component will show from cache.
** Keep alive only cache states. So for keep scroll, you should store last position as state and set scroll position on activated hook.
** Lifecycle doesn't work on cache mode and you should use activated and deactivated hooks.
The HTML5 History API seems made for this.
When formdata is changed save it in the history with:
window.history.replaceState(state, "");
And when (re)loading the page load from history with
window.history.state
Or look into window.addEventListener("popstate", function(){...})
E.g. in my listview I use:
const methods = {
saveStateToHistory() {
var state = window.history.state || {};
if( ! state[this.id]) {
state[this.id] = {};
}
state[this.id].currentPage = this.currentPage;
state[this.id].currentPerPage = this.currentPerPage;
state[this.id].collFilter = this.collFilter;
window.history.replaceState(state, "");
},
};
const watch = {
currentPage(newdata) {
this.saveStateToHistory();
},
currentPerPage(newdata) {
this.saveStateToHistory();
},
collFilter(newdata) {
this.saveStateToHistory();
},
};
function created() {
var state = window.history.state || {};
var myState = state[this.id];
if(myState) {
this.currentPage = myState.currentPage;
this.currentPerPage = myState.currentPerPage;
this.collFilter = myState.collFilter;
}
};
This keeps the current page and selected filters after someone folows a link from the list-view and then uses the browser back-button to go back to the list-view.
I'm using state[this.id] to allow for history state from different components in the same page.