How to async global Mixin data in Nuxt JS? - javascript

First, let me share my workflow. In my nuxt app, I am trying to track if the user is from desktop or mobile by getting the window width of the user. To do that,
Firstly, I am using js's window object in my default.vue to update the height and width variable in my store. Here is the code
//default.vue
created() {
if (process.browser) {
window.addEventListener('resize', this.handleResize);
this.handleResize(window.innerHeight, window.innerWidth);
}
},
}
methods: {
handleResize() {
this.$store.commit('setwindowheightwidth', {
height: window.innerHeight,
width: window.innerWidth
})
},
}
After that, I have created a plugin to keep my mixins. And in the mixin, I am populating my isMobile variable by getting the width variable value from store.
import Vue from "vue"
export default ({store}) => {
// Make sure to pick a unique name for the flag
// so it won't conflict with any other mixin.
if (!Vue.__my_mixin__) {
Vue.__my_mixin__ = true
Vue.mixin({
data: function() {
return {
isMobile: store.getters.windowWidth<768,
}
},
}) // Set up your mixin then
}
}
Now I am getting this data in my every component and pages, just as I was intending. But when I am loading the page first time, or refreshing the page, the value is returning true! Even when the actual value is false. But if I go to other page by navigating or come back to the initial one (without refreshing), I am getting the actual value. So it seems for some reason the value is not updating on initial loading of any of my pages. Usually I fix this kind of issue by getting the data using async-await, but not sure where to use that here. How can I update the mixin data from it's inital state on my page load?

I think, if you use computed property, instead of data value, your problem will be solved.
Also, you can install #nuxt-device module and use it to detect current device in each page.
If these approaches don't solved your problem, just store the values in the state and persist them by cookies.
import Vue from "vue"
export default ({store}) => {
// Make sure to pick a unique name for the flag
// so it won't conflict with any other mixin.
if (!Vue.__my_mixin__) {
Vue.__my_mixin__ = true
Vue.mixin({
computed:{
isMobile(){
return store.getters.windowWidth<768;
}
}
}) // Set up your mixin then
}
}

Related

What is the best way to update Vue data from outside the app?

const app = createApp({
data() {
return {
some_id: 0
}
}
})
I have an autocomplete on a field.
When a label is selected, I want to pass the id to a Vue app.
onSelectItem: ({label, value}) => {
app.some_id = value;
}
This worked in an old v2 version of Vue.js.
Now, I can't even call the methods of the Vue app from other JavaScript functions.
What is the best solution?
There are certain circumstances where you may need to access and mutate, change the instance's data.
This was easier in Vue JS 2, but Vue JS 3 has become more encapsulated. However it does not mean mutating state from outside is impossible. You can read about it here
Supposing that you are using Vue with build steps, which covers most cases, you will have something like this:
const app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you head to browser console and type app, it will be null because it is limited to the scope of compiled .js files.
But if you attach app to a global object, document for example:
document.app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you type app in the console, it will no longer be null, but the Vue instance. From there, you can access the instance's data as well as mutate it via app.$data property.
Now what if the instance has components and you want to mutate their $data? In previous versions, it was possible to access children via $children property. But now in order to access children, you have to give each of them a ref, then access via their ref name. For example:
app.$refs.alertComponent.$data.message = "New message!"

Vue: route navigation with router.resolve (to open a page in new tab) with data in params is lost

I have a component, lets call component 1. A method in component1 makes an axios.post request and the server returns a bunch of data. When data is loaded, a new button appears. When this button is clicked, it will be navigated to another route with another component, let call this component2. Now some of the loaded data from component1 needs to transferred to component2 and should be opened in new tab. Below is the code:
<script>
import axios from 'axios';
export default {
name: "CheckStandard",
data() {
return {
standard: '',
time: {},
programs: {},
example: {},
}
},
methods: {
checkData(){
let std= {
std: this.standard,
}
axios.post('http://localhost:3000/postdata', std)
.then(res => {
if (res.status === 200) {
if (res.data === 0) {
this.invalidID = "This Standard does not exist"
}
else {
let data = res.data
this.time = res.data["Starttime"];
this.programs = res.data["program"]
this.example = res.data["example"]
}
}).catch(error => {
console.log(error);
this.error = error.response
})
},
}
goToPictures(){
let route = this.$router.resolve({
name:'ProgramCheckList',
params: {
programs: this.programs,
time: this.time,
example: this.example
}
})
window.open(route.href,'_blank')
},
}
}
</script>
The function goToPictures is the function that is invoked after clicking the button. Now in this function goToPictures I have defined the route to navigate to another tab. But the problem the data in the params which it should carry is lost. I tried with $router.push ofcourse it works but it is not to open in new tab. Below is the code for the same:
goToPictures(){
this.$router.resolve({
name:'ProgramCheckList',
params: {
programs: this.programs,
time: this.time,
example: this.example
}
})
},
}
Since I am new to vue, I have tried my best to look for an answer for this, even I have came across some posts in several forums mentioning, it is may be not be possible even, instead advised to use vuex. But I still wanted to post it, maybe we have a solution now or any other idea. Thanks
The problem you're seeing stems from the fact that, when you open a new window, Vue is basically going to re-render your components as if you hit refresh. Your Component 2 has props that it can only inherit from another component, and as such, it has no possible way of knowing what the props it needs to use are.
To illustrate in simple terms what's happening:
The user navigates to Component 1. They click the button, which makes the GET request. You now have some data that you can pass onto Component 2 as props.
In a regular environment, the user would simply click on the link leading to Component 2, and the props would be passed on normally, and everything would work as intended.
The problem in your situation is that Component 2 depends on Component 1 for its data. By navigating directly to the Component 2 route (in this situation, opening a new window is functionally identical to a user copy/pasting the url into the adress bar), Vue never has the chance of interacting with Component 1, and never gets told where to get the props it needs to populate Component 2.
Overcoming the issue
There's a few things you can do here to overcome this issue. The most obvious one is to simply open Component 2 as you would normally, without opening a new window, but keep in mind that even if you do this, should a user copy/paste the URL where Component 2 is, they'll run into the exact same issue.
To properly deal with the issue, you have to specify a way for Component 2 to grab the data it needs. Since the data is already fetched, it makes sense to do this in the created() or mounted() hooks, though if you wanted to you could also deal with this in Vue Router's beforeRouteEnter() hook.
Vuex
While you don't necessarily need a state management tool like Vuex, it's probably the simplest way for your needs. When you grab the data from Component 1, store it and access it from the Component 2 mounted() hook. Easy-peasy.
localStorage()
Alternatively, depending on how the data is being served, you could use localStorage(). Since you're opening a new window, sessionStorage() won't work. Do note that localStorage() can only hold strings and nothing else, and isn't necessarily available in every browser.
You can store the data to a global variable and use that in the newly opened window. Asked here Can I pass a JavaScript variable to another browser window?
Provided the windows are from the same security domain, and you have a reference to the other window, yes.
Javascript's open() method returns a reference to the window created (or existing window if it reuses an existing one). Each window created in such a way gets a property applied to it "window.opener" pointing to the window which created it.
Either can then use the DOM (security depending) to access properties of the other one, or its documents,frames etc.
Another example from same thread:
var thisIsAnObject = {foo:'bar'};
var w = window.open("http://example.com");
w.myVariable = thisIsAnObject;
//or this from the new window
var myVariable = window.opener.thisIsAnObject;

Custom react hook not updating it's value

So I have the following custom hook, I want to use it to enable/disable some buttons based on various triggers inside the app.
import { useState } from 'react';
interface IDisableProps {
buttonsDisabled: boolean;
toggleButtons: (isDisabled: boolean) => void;
}
export default function useDisable(): IDisableProps {
const [buttonsDisabled, setButtonsDisabled] = useState<boolean>(false);
const toggleButtons = (isDisabled: boolean) => {
setButtonsDisabled(isDisabled);
};
return {
buttonsDisabled,
toggleButtons,
};
}
One of the places I'm using it from is another hook, where I declare it as
const { buttonsDisabled, toggleButtons } = useDisable(); then use it at the right moment like
if (!buttonsDisabled) {
toggleButtons(true);
}
However, the state always remains the initial one. Upon entering with debugger in toggleButtons I see that in the local scope, this is undefined and can't see the value of buttonsDisabled. What am I missing here? Did I take the wrong approach?
From Building Your Own Hooks – React
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
So when you say "one of the places [you're] using it from is another hook" you are creating an additional [buttonsDisabled, setButtonsDisabled] in memory and not referencing the same state created in the other place(s) you've invoked useDisable.
Two ways to share state are 1) passing props around and 2) using Context.
try with
const toggleButtons = useMemo(isDisabled: boolean) => {
setButtonsDisabled(isDisabled)
}, [isDisabled]);

NUXT: Using async fetch as mixin, how to access page data to overwrite

I have a masthead component that I've been using in pages and since they get destroyed on page change, I've decided to move the component up into the layout so that during route change, it persists (and I can animate/transition accordingly). I am using VueX to update the content of the masthead (imagery, copy, etc...).
Right now I am using an async fetch in all of the pages to make the appropriate call to the store (to update or hide the masthead).
I would like to eventually move the fetch into a mixin and import it to each page versus having to place it in each page manually. The question is, I want to use the mixin to retrieve some page specific data before the call is made. I can always default it in the mixin, but when it comes to overwriting from within the page - I can't access the page data() or asyncData() from fetch().
What would be the best way to access page specific data in fetch()?
My mixin:
export default {
data() {
return {
masthead: false
}
},
async fetch({ state }) {
await state.commit('masthead/UPDATE_MASTHEAD', this.masthead);
}
}
In Page:
import masthead from '~/mixins/masthead'
export default {
name: 'homepage',
mixins: [masthead]
data() {
// Why can't I access this in fetch
return {
masthead: true
};
},
asyncData() {
// Why can't I access this in fetch?
return {
masthead: true
};
},
Thanks!

How to get changes in Vue updated hook?

If I have a Vue component like:
<script>
export default {
updated() {
// do something here...
}
};
</script>
is there anyway to get the changes that resulted in the update? Like how watch hooks accept arguments for previous and next data?
watch: {
someProp(next, prev) {
// you can compare states here
}
}
React seems to do this in componentDidUpdate hooks, so I'm assuming Vue has something similar but I could be wrong.
The updated lifecycle hook doesn't provide any information on what caused the Vue component instance to be updated. The best way to react to data changes is by using watchers.
However, if you're trying to investigate what caused an update for debugging purposes, you can store a reference to the state of the Vue instance's data and compare it to the state when updated.
Here's an example script using lodash to log the name of the property that changed, triggering the update:
updated() {
if (!this._priorState) {
this._priorState = this.$options.data();
}
let self = this;
let changedProp = _.findKey(this._data, (val, key) => {
return !_.isEqual(val, self._priorState[key]);
});
this._priorState = {...this._data};
console.log(changedProp);
},
This works because properties prepended with the underscore character are reserved for internal use and are not available for binding. This could be saved in a mixin to use whenever you needed to debug a Vue component this way.
Here's a working fiddle for that example.

Categories