How do i prevent duplicate calling of asyncData in Nuxt.js - javascript

nuxt.js v2.15.3.
I have used asyncData in some pages and these pages accessing by nuxt-link.
but if continuous clicking of nuxt-link (clicking interval less 1000ms), asyncData function is continuous called.
is there prevent this situation? Do i use fetch instead asyncData?
<!-- some component -->
<nuxt-link :to="`/some-page`"></nuxt-link>
// some page component
async asyncData({ $axios ) {
// Called as much as clicking.
const resp = $axios.get(....)
return { data :resp.data }
}

You can disable the link as soon as clicked:
// some component
<nuxt-link to="/some-page" #click="onLinkClick"></nuxt-link>
data() {
return {
linkClicked: false,
}
}
onLinkClick(e) {
e.preventDefault();
if (linkClicked) return;
this.linkClicked = true;
}
Of course, this solution is assuming you only want to click the link once

fetch() may indeed help since it will move out of the page and remove the blocking effect of it.
You can also implement a debounce method or look to disable <nuxt-link> is a specific condition is met. Check my answer here for debounce: https://stackoverflow.com/a/67209352/8816585
Also, feel free to add some CSS to the button (if you want to stick with asyncData), with something like #null's solution
<nuxt-link to="/some-page"
#click="onLinkClick"
:disabled="linkClicked"
>
My fancy link
</nuxt-link>

Related

DOM elements manipulation with javascript persists after reload or going to next page in pagination

I am trying to toggle a view between grid and list view mode on my frontend HTML page. I am able to do this fine with dom and HTML classes manipulation by toggling "display: none" between two containers. However, when I go to the next product page(through pagination) or when I reload the page, the default view is the one that appears and not the one that was last toggled. Is there a way to persist the view in case a page reload or product pagination changes? thank you.
here is the dom code that achieves this :
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
});
So far I found that I have to use localStorage to achieve this. but is there a better way to do this?
Essentially what is happening is when you request something from the server, the server responds with an HTML document, and whichever scripts associated with that document is run, So whatever JS executed in the first request is not in context when the second request(paginate or reload) is made.
So you need a way to persist information across these page loads, For that, you have 3 options.
Use sessionStorage.
Use localStorage
Use Cookies.
Of the 3 above the easiest would be to use either option 1 or 2.
Replying to your comment,
Also, If I am using localStorage, What am I using to store the view state?
I'm not quite clear as to what you mean by "What you are using to store the state" If your question is about where your data is stored, you need not worry about it as this is handled by the browser. If your question is about "How" to store it you can go through the MDN docs attached in option 1 or 2. This is simply storing a key-value pair as shown in the docs
localStorage.setItem('preferedView', 'grid'); You can add this to your on click handlers as follows,
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
localStorage.setItem('preferedView', 'grid');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
localStorage.setItem('preferedView', 'list');
});
Then when loading a new page at the top of your script you can get the users preferedView(if existing) via const preferedView = localStorage.getItem('preferedView');
Here is a complete example from MDN
In order for anyone to find an answer for a similar task, thanks to #Umendra insight, I was able to solve this by using this :
function viewToggeler(viewBtn1, viewBtn2, view1, view2, viewStord) {
viewBtn2.classList.add('view__active');
viewBtn1.classList.remove('view__active');
view1.classList.add('hidden');
view2.classList.remove('hidden');
sessionStorage.setItem('preferedView', viewStord);
}
viewList.addEventListener('click', () => {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
});
viewGrid.addEventListener('click', () => {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
});
if (sessionStorage.getItem('preferedView') === 'grid') {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
} else if (sessionStorage.getItem('preferedView') === 'list') {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
}
I ended up using sessionStorage over localStorage because it empties itself on window/tab closing which might be the most desirable result. localStorage persists even after exiting the browser and opening it back.
Also, at any point someone wants to empty the sessionStorage on exit, I used :
window.addEventListener('onbeforeunload', () => {
sessionStorage.removeItem('preferedView');
});

How do you change Snipcart's default $localize("actions.continue_shopping") label text?

The following code snippet, as provided by Snipcart's documentation for v3, does not seem to override their default text label: Continue shopping
document.addEventListener('snipcart.ready', () => {
Snipcart.api.session.setLanguage('en', {
actions: {
continue_shopping: "Go back to store"
}
});
});
I have placed the above code segment directly underneath the following...
<div hidden id="snipcart" data-api-key="[Testing API Key]"></div>
<script src="https://cdn.snipcart.com/themes/v3.0.6/default/snipcart.js"></script>
When I click to activate Snipcart's checkout modal, the default label Continue shopping remains (instead of, Go back to store).
The issue mentioned has been fixed in V3.0.10. The sample code from the question will work as-is.
There was an issue with older releases of Snipcart: if localizations are applied too early with the JS API, they'll get overridden when the localization file is loaded.
To ensure everything loads in order, you can update your code to use the Snipcart.ready promise:
document.addEventListener('snipcart.ready', () => {
Snipcart.ready.then(function() {
Snipcart.api.session.setLanguage('en', {
actions: {
continue_shopping: "Go back to store"
}
});
});
});

v-img load dynamic images raise eror [Vuetify] Image load failed

In my case, I have two unrelated components. The first component has a button. The second component has <v-img> element of the Vuetify framework. The version of Vuetify which I use is 2.2.4. When the user clicks the button I pass some paraments by EventBus. I want to change the image in the second component after that click. For some reason in console I see that error: [Vuetify] Image load failed. eager property don't work. I also changed v-img to img. The result is the same. The image doesn't change. How to solve this problem for your opinion?
The first component (button click event logic):
EventBus.$emit('showLegend', {
imageName: 'http://domain/logo.png',
legendVisible: true
})
The second component:
<template>
<v-img
eager
v-if="legendVisible"
:src="legendURL">
</v-img>
</template>
<script>
import { EventBus } from '../../services/events.js'
export default {
data () {
return {
legendURL: 'http://domain/logo.png',
legendVisible: true
}
},
created () {
EventBus.$on('showLegend', data => {
if (data) {
this.legendURL = data.imageName
this.legendVisible = data.legendVisible
}
})
}
}
</script>
So you are saying that the image is bound correctly and displays, but the issue is with the change in URL after the emit via EventBus happened? If it had not worked in the first place, I'd have assumed you might need something related to require. Many times for static images that's the issue. You'd have to write
<v-img src="require('http://domain/logo.png')"></v-img>
However, I could not get this syntax to work with my own dynamic variables coming from a v-for="card in cards" myself until I used
<v-img :src="require('#/assets/' + card.imgSrc + '.jpg')"></v-img>
which I find rather dissatisfying.

Responding to a click on a Vue element programmatically

Essentially, I want my Vue instance to respond to a click on an uploaded thumbnail.
I'm using the FineUploader Vue package with the template layout per the docs (see end of the question). Upon uploading an image, a tree like this is outputted:
<Root>
<Gallery>
<Thumbnail>
</Gallery>
</Root>
Coming from a jQuery background I really have no idea about the 'correct' way to go about this given that the Thumbnail Template is defined by the package already, and so I'm not creating my own Thumbnail template. I know that I can access elements like this:
let thumb = this.$el.querySelector('.vue-fine-uploader-thumbnail');
And perhaps a listener
thumb.addEventListener('click', function() {
alert('I got clicked');
});
But dealing with the Vue instance being re-rendered etc. I'm not familiar with.
Vue Template:
<template>
<Gallery :uploader="uploader" />
</template>
<script>
import FineUploaderTraditional from 'fine-uploader-wrappers'
import Gallery from 'vue-fineuploader/gallery'
export default {
components: {
Gallery
},
data () {
const uploader = new FineUploaderTraditional({
options: {/*snip*/}
})
return {
uploader
}
}
}
</script>
In order to respond to click events you add the v-on:click (or it's short form: #click) to whatever tag you want.
If you have elements that are nested that respond to the click event you might experience that a click on a child triggers a parents click event. To prevent this you add #click.stop instead, so that it doesn't trigger the parents click.
So you would have something along the lines of:
<Gallery #click="myFunction" />

Update React component when state changes, using a reference to itself

In a React component, I define how the component handles state change in a callback to setState(). This seems wrong / against flux convention. Where is the correct place to define how an element should behave given a new state?
I'm assuming it's in the render() method or the didMount() method.
In this situation, I need to call a JS method on the DOM element:
if (this.state.play) {
document.querySelector(".VideoPlayer-Video").play();
} else {
document.querySelect(".VideoPlayer-Video").pause();
}
I can't do this before the component has rendered. So how & where should this be done?
Example Details:
This is a very simple react component. It has a <video> element, and a button that pauses or plays it.
The state only has one attribute, "play", which is "true" if the video is playing or false if the video is paused.
When the "play" button is clicked, I flip the state, then do a DOM query to get the video element, and call a method on it.
This seems unconventional in React, since I am telling the component how to respond to a state change inside of a click handler. I would expect to define how the component responds to state change elsewhere, but I'm not sure where.
Where is the conventional place to tell the video element to play or not in response to state change? How can I reference the DOM element there?
Code:
var React = require('react');
module.exports = React.createClass({
getInitialState: function(){
return {
play: true
}
},
render: function(){
return <div className="VideoPlayer">
<video className="VideoPlayer-Video" src="/videos/my-video.mov"></video>
<button className="VideoPlayer-PlayButton" onClick={this.handlePlayButtonClick}>
{this.state.play ? "Pause" : "Play"}
</button>
</div>
},
handlePlayButtonClick: function(){
this.setState({
play: !this.state.play
}), function(){
var video = document.querySelector(".VideoPlayer-Video");
if (this.state.play) {
video.play();
} else {
video.pause();
}
}
}
});
Using componentDidUpdate seems appropriate.
1.Click
2. Change state -> trigger - rerender
3. Just after your component is mounted call the right function for video
componentDidUpdate: function() {
if (this.state.play) {
this.refs.MyVideo.play();
} else {
this.refs.MyVideo.pause();
}
}
If you need to control those function for the very first rendering use componentDidMount.
Simple and clean in my opinion.
Edit: I edited my code using ref, I think this is indeed the right way to go
https://facebook.github.io/react/docs/more-about-refs.html
Just place a ref in your video component:
<video ref="MyVideo"> </video>
Updated after mark's comment
Flux is good consideration for long term components, yes it's very big and requires some breaking changes, as François Richard noticed. But still worth it.
If you have a big component, then here's some tips:
You change state of that video component to playing, ONLY when video component receives $event, otherwise it doesn't make sense, as flux heavily uses nodejs eventEmitter, note video components must be explicitly separated, so when you expand your functionality it won't hurt your component.
Simple demo repo.
Methods performed on the DOM should be called within componentDidUpdate, as it is called once react has finished its updates to the DOM.

Categories

Resources