Nuxt.js vuex store not persisted - vue.js

I've got some strange issues with my Nuxt.js Setup.
Some States in Store arent persistent, everytime I load another view, they went back to the default value.
pages/test.vue
<template>
<section class="section">
<b-container>
<b-row>
<b-col cols=12>
<b-button #click="setTest" variant="dark">test</b-button> | {{this.$store.state.test}} |
</b-col>
</b-row>
</b-container>
</section>
</template>
<script>
export default {
name: 'test',
methods: {
setTest() {
this.$store.commit("setTest")
},
}
}
</script>
store/index.js
export const state = () => ({
test: "test"
})
export const mutations = {
setTest: (state) => state.test = 'Hello'
}
Testscenario is to hit the "test"-button who call the method with mutation-commit "setTest" which set the state to "Hello". Currently it works fine, but if I changed the view or reload the page, the state is set to default "test".
What am I doing wrong?

Alright, so the behavior is totally logic. Vuex/Pinia are not supposed to be persistent.
For any persistence on the front-end, you need either:
cookies
localStorage
pass it in the URL (query params)
IndexedDB
get the data back from making a call to a backend
If you are using Vuex or Pinia, there are also packages that you could use to get an easier time (to sync your store into something persistent automatically).
Some of the packages here may be useful: https://github.com/vuejs/awesome-vue#persistence
For pinia: https://stackoverflow.com/a/73863929/8816585
If you reload your page with an F5, all your JS will be wiped and loaded again. Hence, no state will be kept since it will be a brand new page. When working with frontend frameworks, you cannot expect it to just work after a page refresh.
Same go when you do follow an href, it is an actual real navigation. What you need to do, is to use a <nuxt-link></nuxt-link> component, with something like to="/profile" to let VueJS move to this URL.
NuxtLink is a Nuxt.js component and essentially a component on top of <router-link></router-link>, which is Vue router.
TLDR: you cannot use things like window.location.href, nor <a href="...". You may use the given components by either Nuxt (nuxt-link) or Vue's router (router-link), if you're using VueJS only.
Giving a read to the Vue router's documentation may be a good start to understand things a bit more !
If you're using nuxt/auth, give a try to that one: https://auth.nuxtjs.org/api/storage/#universal-storage

Related

How can I refresh a route every time it is accessed in Vue Router 4?

I have setup a route in vue-router 4 that should load a component dynamically depending on whether the user is logged in or not. I did it like this (there may be a better way?):
import Personal from '../views/Personal.vue';
import Public from '../views/Public.vue';
routes: [
{
path: '/',
component: async () => {
const isLoggedIn = await authenticateUser();
if (isLoggedIn == true) {
return Personal
} else {
return Public
}
}
}
]
The App.vue file is this:
<template>
<div id="app">
<Site-Header></Site-Header>
<router-view></router-view>
<Site-Footer></Site-Footer>
</div>
</template>
The problem is that if a user logs in from the homepage route with path of '/', he doesn't navigate away from this route. Instead I would like vue-router to just load the Personal component instead.
The switch between Personal and Public only seems to work if I hard refresh the page, otherwise no changes happen. So if a user logs in, they still see the Public.vue component, then after a page refresh they see the Personal.vue component. If they then logout, they still see the Personal.vue component until they refresh the page.
How could I force vue-router to analyse the route after log-in/log-out and load the correct component?
To have multiple routes utilizing the same path, your best bet is using Named Views. You can define the default component for your index, or / path to be your Public component, while conditionally selecting a different component using v-if in your template.
You could define your routes as:
routes: [
{
components: {
default: Public,
Personal: Personal
},
name: "index",
path: "/"
}
]
Important to note that the syntax here differs. The component field here has to be pluralized in order for this to work, so it has to be components.
Then in your root template that's calling the views, you can then use a simple v-if to switch between them, depending on whether the user is logged in or not. How you store that information is up to you, so just adapt the below code to reflect your own app's logic
<!-- The default is Public, so you don't have to provide the name here -->
<router-view v-if="!user.loggedIn" />
<router-view v-else name="Personal" />
You can see this in this codesandbox

Calendly widget doesn't work after page refresh when live

The calendly widget works at first, but if you refresh the page it stops working but only when the website is live. In local development, no such issue occurs.
Also noticed that when I route to the page through navigation, it works. But if I enter the link to the specific page directly, it doesn't work.
Here's the code:
<template>
<client-only>
<vue-calendly url="link" :height="650"></vue-calendly>
</client-only>
</template>
<script>
import Vue from 'vue';
export default {
created() {
if (process.isClient) {
const VueCalendly = require('vue-calendly').default;
Vue.use(VueCalendly);
}
}
};
</script>
The Vue application is running on Gridsome so it's SSR. I set the widget to only display in client side. Not sure what the issue is.
There is a solution possible to integrate Calendly without using their widget. You can try it as well. This solution should not produce the error mentioned and was tried in an SSR application.
<template>
<!-- Calendly inline widget begin -->
<div class="calendly-inline-widget" data-url="YOUR_CALENDLY_URL" style="min-width:320px;height:630px;"></div>
<!-- Calendly inline widget end -->
</template>
<script>
export default {
mounted () {
const recaptchaScript = document.createElement('script')
recaptchaScript.setAttribute('src', 'https://assets.calendly.com/assets/external/widget.js')
document.head.appendChild(recaptchaScript)
}
}
</script>
From this link, we can see that he is importing the component with
import Vue from 'vue';
import VueCalendly from 'vue-calendly';
Vue.use(VueCalendly);
Then with
<vue-calendly url="your Calendly URL" :height="600"></vue-calendly>
I'm not sure if you are trying to use a syntax like es2020 import here but the require('vue-calendly').default is probably the issue here.
Try importing it in the basic way as suggested above, and then you will be able to make some lazy-loading of it later on if you wish.
Also, you may use your devtools to see why your Calendly instance is not present.
Addy Osmani did a great article on how to import on interaction if you're interested into optimizing your loading time. If it's not that much needed, simply use the usual method or even simpler, load the vanilla JS solution.

How can I prevent vue-router from replacing the content loaded into one <router-view> when I load content into another <router-view>?

My Vue app contains two main router-views: one inside of a modal, allowing content to be loaded inside said modal, and another inside the main page. I differentiate between them using the name attribute. Here is a piece of code in the routes.js file which would load content into the modal, for example.
export default ({
mode: 'history',
routes: [
{
path: '/classComp/create',
components: {
modalView: createClass
}
}
]
})
Essentially, my issue is that, when I load content into the router, the content in the router-view for the main page disappears. I understand that most people use the children attribute to address this, but that is not feasible in this case. For instance, the user can press a router-link button in the sidebar to load the form to create classes into the modal. The ability to press this button is thus unrelated to what is loaded or not loaded into the main page, so creating a children attribute is not feasible.
In short, how can I get vue-router to load content into the modal's router-view without wiping the content of the mainbox's router-view?
**EDIT: **Someone suggested to use keep-alive for this, but I could not get this to work. Is this a viable suggestion, and, if so, how would I go about it?
Thinking over your use-case again, I don't think nested routes it the way to go.
Unless you really want to have a route that points to your modal, you can use something like portal-vue to push the modal into another view.
If you are using vue 3, it comes with a component that accomplishes something similar: Teleport
EDIT - continuing our conversation from the comments
As I understand it, portal allows you to insert HTML into two separate locations at once from a single route; my issue is that I need to have 2 routes loaded simultaneously.
You're right, portal won't really allow you to change that. The problem is that vue-router does not have native support for simultaneously loading two routes (unless one is a child of the other). A couple things you could try:
Add a modal nested route (route.children). This would allow you to use <router-view name="modal"> and not navigate away from the parent view. I don't think this makes a lot of sense though. You could do it programmatically using router.addRoutes
Have two routers and two vue apps. I don't believe that vue-router has native support for loading two routes at the same time (unless one is a child of the other). You could however have a separate vue instance just for your modal code. This might introduce complexities based on the design of your app though. A quick example:
const appRouter = new VueRouter({ ... })
const modalRouter = new VueRouter({ mode: 'abstract', ... })
Vue.prototype.$modalRouter = modalRouter
const mainApp = new Vue({ router: appRouter })
mainApp.mount('#app')
....
App.vue
-------
<template>
<div id="app">
<router-view></router-view>
<div id="modal-app" v-pre>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
mounted() {
const modalApp = new Vue({ router: this.$modalRouter })
modalApp.mount('#modal-app')
}
}
</script>
You could call this.$modalRouter.push anywhere in your app to update the route for it.
Both of those solutions seem a bit hacky to me. The easiest way to do this is probably not to use vue-router at all. With portal-vue you could mount content into your modal from anywhere in the app. You can even do it programmatically with Wormhole.open().

Keep alive view-router by key param

How do I keep vue-router alive with different params separately?
TL:DR:
Let's consider an example when we're developing a website like facebook. Each user has a profile page. Because there are a lot of users we don't want to iterate all users and load all profile page on load like bellow
<template v-for="profile in profilePages">
<profile-page :data="profile" v-show="this.route.params['id'] === channel.id"/>
</template>
The common approach would be:
router.js:
{
component: ProfileWrapper,
path: '/profile',
children: [
{
path: ':id',
component: ProfilePage
}
]
}
ChannelsPage:
<keep-alive>
<router-view :=key="$route.fullPath"></router-view>
</keep-alive>
But here's the issue. Since user visits someone's page and navigates away, I want the router to keep it alive in cache somewhere, or just hide it. In my particular case, user visits 2-3 profile at most and switches a lot between them. And switching operation is time costly, because there are a lot of DOM in it.
Can I do it with vue-router and keep-alive?
EDIT:
Please check the sandbox. Each time you switch between pages (#1,#2,#3,#4) Vue creates new components ProfileInnerComponent from the scratch (not from the cache like v-show). That's noticeably by checking red div, the create hook of ProfileInnerComponent is called, which emits the event, and App adds the div with current time.
I changed your sandbox by adding this codes:
// in App.vue
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
and
//in Profile.vue
<keep-alive>
<profile-inner-component v-for="i in comps" :key="i" :data="i"/>
</keep-alive>
In order this to work you need unique names on your components, which you would then use the include property on <keep-alive>.
<keep-alive include="Foo,Bar">
...
</keep-alive>
In you case, you would be better served using dynamic components rather than a single route.
<component :is="$route.params.id"></component>
keep-alive with Dynamic Components
keep-alive API reference
update
Pre-fetching channel content based on the query param id:
// routes.js
routes = [
{
path: '/channel/:id',
name: 'show.channel',
props: true,
component: Channel
}
...
]
// Channel.vue
import axios from 'axios'
export default {
data () {
return {
content: ''
}
}
beforeRouteEnter(to,from,next) {
axios.get('/api/channel/' + to.params.id).then(response => {
next(vm => {
vm.content = reponse.data
})
})
},
watch: {
'$route' (to, from) {
// fetch new channel content when a query param is changed.
}
}
}
I am not sure if i understand correctly but lets suppose you have 2 pages.
Admins and Users and each page has an counter which is initially 0.
So you want when you are in Admins page and you increasing the counter,when navigating to another page and return back to Admins page the counter to be as you have left it.
I made a sandbox for this.Also please keep open the console to see how many times the components is rendered.
NOTE that in the sandbox example,is illustrated the logic how to achieve this.Never use keep-alive in router-view in App.vue.

How would a WebTorrent VueJS component could be build?

Hy there. Given webtorrent.io I would like to build a VueJS component that shows loading magnet files, and status and also when downloads finishes triggers players.
Is the Vuex Store a good place to keep a list of active download queues and a stream o data?
All that is possible with webtorrent.
var WebTorrent = require('webtorrent')
var client = new WebTorrent()
var magnetURI = 'magnet: ...'
client.add(magnetURI, { path: '/path/to/folder' }, function (torrent) {
torrent.on('done', function () {
console.log('torrent download finished')
})
})
How could I structure that architecture/pattern with VueJS? Any insights, apreciated
Thanks!
I will answer cause I found a solution. Then if the community wants this to be deleted, its all right.
Basically what I did is wait for a Component to be mounted.
Inside AComponent.Vue
<template>
<keep-alive>
<!-- HTML binding webtorrent client data -->
</keep-alive>
</template>
<script>
export default {
mounted() {
var client = WebTorrent()
}
}
</script>
Why this achieve what I was looking for ? :
keep alive keep that parts of the DOM alive, when i render another template and come back to this one (as if the download queue is in a widget, modal, or another view that needs to keep alive).
mounted, its part of the component lifecycle hooks (https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram), and happens once the DOM has been maniuplated, window exists (needed by WebTorrent), and the component has been rendered.