Nuxt, Vuex data clears on browser refresh - javascript

I have an app using Nuxt on the front-end. Since Nuxt is a SSR technology the data in vuex stores gets deleted after refreshing the browser. Is there a way to save and keep vuex data in browser storage (preferably not cookies) even after refreshing the browser?

We use vuex-persist to save some store information in indexDB to provide offline feature (PWA) but you can use localstorage and cookies as well. Its pretty forward to use and also have an nuxt implementation:
// Inside - nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/vuex-persist', ssr: false }
]
}
// ~/plugins/vuex-persist.js
import VuexPersistence from 'vuex-persist'
export default ({ store }) => {
new VuexPersistence({
/* your options */
key: 'vuex', // The key to store the state on in the storage provider.
storage: window.localStorage, // or window.sessionStorage or localForage
}).plugin(store);
}

You cannot save your Vuex state without any kind of browser storage. Depending on what you need to store, you could use:
localStorage if it's some small information, some setting or alike
make an API call to your backend and repopulate your Vuex store accordingly (best solution, especially if you stored some JWT token in your localStorage)
IndexedDB API, never used it but it's some kind of light database
Still, storing your whole Vuex state is not a thing to do in the browser for various reasons (security, performance, principle etc...).

In Addition to kissu's answer. You can use replaceState to restore a saved state.
But you probably shouldn't, you can create a bug that your user can't get out of by refreshing. And that's really frustrating.
The pattern to do this is fairly simple.
First, in vuex, subscribe to all mutations
https://vuex.vuejs.org/api/#subscribe
const unsubscribe = store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
// you may call unsubscribe to stop the subscription
unsubscribe()
Check which mutations you are interested in saving and store them in localStorage, or IndexDb or whatever you want.
On page load you need to check your storage solution for this data. If it exists. call replaceState
https://vuex.vuejs.org/api/#replacestate
store.replaceState(state: Object) // give it an object structure matching your Vuex module state structure
And this is how you can save and reload state.

Related

mapStateToProps not updating props when localStorage is modified

I have a redux application that requires authentication. Right now I'm trying to make sure the application ensures the user is still logged in whenever they perform an application in the app. Here is the bottom of my App.jsx file:
function mapStateToProps({ offline, user }) {
return {
// we use this to know if we should redirect to auth pages
// we don't want logged in users to be hitting these static pages
loggedIn: !!user.token,
offline,
};
}
const ConnectedApp = connect(mapStateToProps)(App);
export default ConnectedApp;
I test this functionality by clearing the site local storage through the chrome developer tools. And then performing an action on the site. I've placed many different log statements. I find that mapStateToProps is being called, but the props are not changing. The application continues to think that the user is still logged in and the state hasn't changed.
I've been following all the online resources I can find, but nothing seems to be helping. Am I missing something?
That's because mapState only re-runs when an action was dispatched to the Redux store, and a new store state was calculated as a result. Modifying local storage does not involve dispatching a Redux action, so your mapState will never run.
Don't depend on values from localStorage in a mapState function - they should only extract values from the state argument, and return those.

Fetch cached data Vue CLI Vuex PWA

How do I get my cached data from service worker in to the vuex store in offline mode?
The app works in offline mode in the browser, but when I add the site to home screen with the manifest.json file included, it won't show the cached data and only the general js, css and html.
I'm having a hard time figuring out, how I get my cached data from my PWA. I already have the data cached. The problem is retrieving it back to the vuex store state called "games", to display when the app is offline.
The vue.config.js code for caching the api call for the service worker.
module.exports = {
pwa: {
workboxPluginMode: "GenerateSW",
workboxOptions: {
navigateFallback: "/index.html",
runtimeCaching: [{
urlPattern: new RegExp('API_URL'),
handler: 'networkFirst',
options: {
networkTimeoutSeconds: 20,
cacheName: 'api-cache',
cacheableResponse: {
statuses: [0, 200],
},
},
}]
}
}
};
As you can see in the image above the code, it has stored the cache "api-cache" with the objects from the API.
Now I want to use this data in the cache from api-cache on my site, when the site is offline.
So my question is:
How do I get my cached data from service worker in to the vuex store in offline mode, when a user has added the app to their home screen?
You're thinking about this in the wrong way. You don't need the service worker to do anything for you. It's already doing it by providing you with a cache implementation. Instead, you need to use the navigator.onLine hook to determine if they have internet access. If not, then hydrate your store from the cache and make sure to subscribe to any mutations and push the state back into the cache, like this:
if (!navigator.onLine) {
const state = hydrateFromCache()
store.subscribe((mutation, state) => cache.setItems(state)
}
Where the hydrateFromCache method simply pulls the store in from the cache and hydrates the state of all vuex modules.

Reload component with vue-router

I have a project with Vuejs and Vue-Router. When a user pay in (login) system, the server return a file Json with token, username and first name, this file is stored in localstorage for be used in following requests. The first name is used in layout for show a message welcome in the navbar. After logout, localstorage is cleared and the problem is here, when other user pay in the message welcome not reload with new first name.
<v-chip>{{bienvenida}}</v-chip>
computed: {
bienvenida() {
const user = JSON.parse(localStorage.getItem("user"));
return user ? `Bienvenido ${user.firstName}` : "";
}
}
The LocalStorage is not reactive, so the computed property does not react on a change of the localStorage. There are various ways to circumvent this, but the easiest is, to watch an internal property and just persist the values to the localStorage too.
One proper way to do this is to use Vuex (official state management of Vue) to store your data to a Vuex store. There is then a Vuex plugin to persist all of the store or parts of it in the local or session storage. The Vuex store is reactive, so you can just watch changes to the store in your computed property.

LocalStorage with react-redux

I'm using React and Redux for my SPA and I want to store some data locally. And I need to synchronize appState with localstorage, so my data won't be lost after refreshing the page.
I'm completely new for React and Redux and have not much of understanding what's going on, but as I think Redux creates for me that state of entire app, so I can't just bind my state with localstorage in app component because it'll be just state of component and not of my app.
I would suggest storing the data in local storage with the following commands.
Set the data in localStorage can be done with the command:
localStorage.setItem('nameForData', variableNameForData);
To retrieve the data when required.
var variableNameForData = localStorage.getItem('nameForData')
To remove the data from localStorage:
localStorage.removeItem('nameForData')
These would typically be put inside action creators with a dispatch to change the state of some Boolean that tracks the applications interaction with localStorage.
For example you might have a state that is set to true when the local storage is created.
On refresh you might call an action creator that checks the local storage exists, and if it does set that boolean back to true or if it does not exist you are back to creating local storage and then set it to true.
You could put this function in componentWillMount(){} and it will be called when the component is first rendered, and thus in the case of a refresh.
Docs for component life cycle and specifically componentWillMount here
Docs for local storage here
I can suggest you to store the state after each action. For that you can use a localStorage middleware that only store the whole state object.
Then in your createStore part you will retrieve the initialState from the localStorage API.
In that case you don't need to modify any component, juste the initialState from localStorage and the middleware that save the state

React routing and persistence on page refresh

I am using React with react-router and Reflux as my datastore, but I am unsure on how to best deal with persistence to allow page refresh.
My components connect to the store with Reflux.connect, but since the store fetches the data from a backend, it is not available yet when the Components first initialize and render.
When the user enters my app from the start, then all this data is loaded in order and available when it needs to be, but if further down a route you trigger a page refresh, react tries to render components that rely on data that is not there yet.
I solved this by constantly keeping a copy of data in LocalStorage and serving that from the Reflux store getInitialState(), so that all components get the data before they render.
I wonder if this is the proper way to do it. When for some reason the local storage data gets cleared or corrupted, the interface goes blank, because the components cannot access the correct data. Substructures and properties don't exist and trigger javascript errors. It seems like a messy and unreliable solution.
I am curious to know what patterns are used to solve this.
------ edit -----
To answer to the comment of WiredPrairie:
1) Why are you initializing components with data in getInitialState?
When my components use Reflux.connect, they don't have the data in their state yet on the first render as the store still needs to fetch its data. My views currently don't work gracefully with undefined data. By returning the locally stored cache from the Reflux store in getInitialState(), all connected components will get that data before their first render call.
2) What's causing a page refresh and why can't the data be loaded in the same manner as it was the first time?
It's mainly a workaround I had to build around livereload refreshing the page when I make edits (will look into using react-hotloader later but is not an options yet), but users can also just hit refresh when they are somewhere in my nested views and that would have the same effect. When they manually refresh, they are not entering the app at the start.
3) When components are wired to the change events of a store, why don't they update then?
They do update, but like I said they don't deal with empty data right now and on first render they will miss it waiting for the store to fetch things. I can make all my views work gracefully with empty data, but that would add a lot of boilerplate code.
From the replies so far, I get the feeling that what I'm doing with localStorage is the common way to do it. Cache stuff locally in localStorage or sessionStorage or something similar, and serve that data immediately after a refresh.
I should make my views a bit more robust by gracefully handing empty data on the odd occasion that localStorage doesn't work properly.
I've been caching each Store in sessionStorage when its emitChange() fires, and initializing the store from sessionStorage if cached data exists, or null values otherwise. This seems to work provided that the views can handle null values, which is probably a good idea anyway (it sounds like this is your main problem).
I'd suggest making your views handle the case of no data gracefully, initialize everything with null values if the cache isn't available, and then call the backend to update the null values with an Action whenever the data returns.
I haven't tried Reflux, but in regular Flux it would look like this (maybe you can apply the same principle):
var _data;
if (sessionStorage.PostStore)
_data = JSON.parse(sessionStorage.PostStore);
else {
_data = {
posts: null
};
BackendAPI.getPosts(function(err, posts) {
if (posts) {
PostActions.setPosts(posts);
}
});
}
...
AppDispatcher.register(function(payload) {
var action = payload.action;
switch (action.actionType) {
...
case Constants.SET_POSTS:
_data.posts= action.data.posts;
break;
default:
return true
}
// Update cache because state changed
sessionStorage.PostStore = JSON.stringify(_data);
PostStore.emitChange();
return true;
});

Categories