NavigationDuplicated Navigating to current location ("/search") is not allowed - javascript

When I want to do a search multiple times it shows me the NavigationDuplicated error. My search is in the navbar and the way I have configured the search is to take the value using a model and then pass the value as a parameter to the ContentSearched component, and then receive the value of the search in that component.
I know the right way is to use an emitter, but I still don't know how to learn to use it. To access the emit is context.emit('', someValue)
NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/search") is not allowed", stack: "Error↵ at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}
NavBar.vue
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-nav" v-bind:class="{'navbarOpen': show }">
<div class="container">
<router-link to="/" class="navbar-brand">
<img src="../assets/logo.png" alt="Horizon Anime" id="logo">
</router-link>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" v-on:click.prevent="toggleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent" v-bind:class="{'show': show }">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link class="nav-link" to="/" ><i class="fas fa-compass"></i> Series</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'EpisodesSection'}" ><i class="fas fa-compact-disc"></i> Episodios</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'MovieSection'}" ><i class="fas fa-film"></i> Peliculas</router-link>
</li>
</ul>
<div class="search-bar">
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" v-model="query" type="search" placeholder="Buscar películas, series ..." aria-label="Search">
<button class="btn btn-main my-2 my-sm-0" #click.prevent="goto()" type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
</div>
</div>
</nav>
</template>
<script>
import {value} from 'vue-function-api';
import {useRouter} from '#u3u/vue-hooks';
export default {
name: "NavBar",
setup(context){
const {router} = useRouter();
const query = value("");
let show = value(true);
const toggleNavbar = () => show.value = !show.value;
const goto = () =>{
let to = {name: 'ContentSearched' , params:{query: query}}
router.push(to);
};
return{
show,
toggleNavbar,
goto,
query
}
}
}
</script>
ContentSearched.vue
<template>
<div class="container">
<BoxLink/>
<main class="Main">
<div class="alert alert-primary" role="alert">
Resultados para "{{query}}"
</div>
<div v-if="isLoading">
<!-- <img class="loading" src="../assets/loading.gif" alt="loading"> -->
</div>
<div v-else>
<ul class="ListEpisodios AX Rows A06 C04 D02">
<li v-for="(content, index) in contentSearched" :key="index">
<div v-if="content.type === 'serie'">
<Series :series="content"/>
</div>
<div v-if="content.type === 'pelicula'">
<Movies :movies="content"/>
</div>
</li>
</ul>
</div>
</main>
</div>
</template>
<script>
import {onCreated} from "vue-function-api"
import {useState , useRouter , useStore} from '#u3u/vue-hooks';
import BoxLink from "../components/BoxLink";
import Movies from "../components/Movies";
import Series from "../components/Series";
export default{
name: 'ContentSearched',
components:{
BoxLink,
Movies,
Series
},
setup(context){
const store = useStore();
const {route} = useRouter();
const state = {
...useState(['contentSearched' , 'isLoading'])
};
const query = route.value.params.query;
onCreated(() =>{
store.value.dispatch('GET_CONTENT_SEARCH' , query.value);
});
return{
...state,
query,
}
}
};
</script>

This happened to me when I had a router-link pointing to the same route. e.g. /products/1.
The user is able to click on the products, but if a product was already clicked (and the component view was already loaded) and the user attempts to click it again, the error/warning shows in the console.
You can learn more on the github issue..
Posva, one of the main contributors of vue-router suggests:
router.push('your-path').catch(err => {})
However, if you don't want to have a catch block which does nothing, in order to solve the issue you can compare the router navigation with the current route and only navigate if they differ:
const path = `/products/${id}`
if (this.$route.path !== path) this.$router.push(path)
Note: $route is an object provided by vue-router to every component. See The Route Object for more info.

I think the best solution to this problem can be implemented at the root level if we are not going to further use Router.push as asynchronous call.
import Router from 'vue-router';
const originalPush = Router.prototype.push;
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
};
Vue.use(Router);

If you are not feeling comfortable catching all kind of errors, I think this implementation is more considerate:
this.$router.push("path").catch(error => {
if (error.name != "NavigationDuplicated") {
throw error;
}
});

As of 2021, global configuration :
I only wanted to silence NavigationDuplicated error, an empty catch can be dangerous. So I did this :
const router = new VueRouter({/* ... */})
const originalPush = router.push
router.push = function push(location, onResolve, onReject)
{
if (onResolve || onReject) {
return originalPush.call(this, location, onResolve, onReject)
}
return originalPush.call(this, location).catch((err) => {
if (VueRouter.isNavigationFailure(err)) {
return err
}
return Promise.reject(err)
})
}
Insert this once when you initialize vue-router.
Thanks to #Oleg Abrazhaev for the update.

I encountered the same problem while searching. My solution is to add timestamp to the this.$route.query parameters of the search page.
this.$router.push({
path: "/search",
query: {
q: this.searchQuery,
t: new Date().getTime(),
}
});
Hope it helps you.

if you are using router.push in your code and you don't care about the navigation failing, you should catch it by using catch:
router.push('/location').catch(err => {})

in manual:
router.push(location, onComplete?, onAbort?)
You can use more simply
router.push("/", () => {});

You mixed multiple concepts here from router-links to programmatic navigation, to query params to a state store. That makes it a bit difficult to help you and tell you what the "correct" solution here is.
Nonetheless, I think the best approach for you would be to:
1) define your route as
{
path: "/search/:searchString",
component: MySearchComponent,
props: true
}
2) use a responsive <router-link> instead of your router.push
<input type="text" v-model="searchString">
<router-link :to="'/search/'+searchString" tag="button">search</router-link>
3) access the searchString in your search component via props: ['searchString'] and this.searchString
props: ['searchString'],
...
computed: {
msg() {
return `Searching for, ${this.searchString}!`;
}
}
Full example: https://codesandbox.io/s/vue-routing-example-9zc6g
Note, I just forked the first codesandbox with a router I could find, adjust accordingly.

For TypeScript it worked like this
const superPush = VueRouter.prototype.push
VueRouter.prototype.push = async function push(loc:RawLocation):Promise<Route> {
try {
return await superPush.bind(this)(loc)
} catch (e) {
if (e?.name === 'NavigationDuplicated') {
console.warn(e)
return e
} else {
throw e
}
}
}

I'm very late to the party, but I thought I'd add my solution to this issue as it isn't listed: I simply placed an intermediate searching page as a pass-through to the search results view. I am now using this page for doing some preprocessing of the search terms.
The page template simply is:
<template>
<div>searching ...</div>
</template>
The NavigationDuplicated error is now gone and as an added benefit because I perform the fetch in this intermediate page, the responsibility for error-handling is isolated from both the search bar and the results view.

I post here the solution I found, because I was not able to find it well documented somewhere, and I went through it by trial and error.
It could be useful to someone, or someone may fix my misunderstood interpretation of vue-router guards.
It make use of vue-router V4.x and a global beforeEach guard.
The use cases are:
user asks for https://app.com/ without being already authorized;
user asks for https://app.com/ being already authorized;
user asks for any available routing, which requires auth or not.
Routes:
const routes = [
/**
* Routes not requiring auth
*/
{
path: '/',
component: () => import('layouts/NotAuthorizedLayout.vue'),
children: [
{
path: 'login',
name: 'LOGIN',
component: () => import('pages/Login.vue')
},
{
path: 'emailsignup',
component: () => import('pages/EmailSignup.vue')
},
{
path: 'forgottenpassword',
component: () => import('pages/ForgottenPassword.vue')
}
]
},
/**
* Routes requiring auth
*/
{
path: '/',
component: () => import('layouts/AuthorizedLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: 'authors',
name: 'AUTHORS',
component: () => import('pages/Authors.vue')
},
{ path: 'profile', component: () => import('pages/userProfile/index.vue') }
]
}
];
beforeEach global guard:
const redirectToLogin = route => {
const LOGIN = 'LOGIN';
if (route.name != LOGIN) {
return { name: LOGIN, replace: true, query: { redirectFrom: route.fullPath } };
}
};
const redirectToHome = route => {
const DEFAULT = 'AUTHORS';
return { name: DEFAULT, replace: true };
};
Router.beforeEach((to, from) => {
const userIsAuthenticated = store.getters['authentication/userIsAuthenticated'];
const requiresAuth = to.matched.some((route) => route.meta && route.meta.requiresAuth);
if (!userIsAuthenticated && to.fullPath === '/') {
return redirectToLogin(to);
}
if (!userIsAuthenticated && requiresAuth) {
return redirectToLogin(to);
}
if (to.fullPath === '/') {
return redirectToHome(to);
}
return true;
});

Your question is rather old.
Your error is the "#click.prevent". This statement does not work, because your button is a submit button (so your event is called twice).
Use "#submit.prevent" should work (or change the type of your button).

My solution is a mix of extending prototype with check Navigation Duplicated Error. Other errors and warning should be visible. After a week on production - no NavigationDuplicated and everything is working.
import { equals } from 'ramda'
export function register(Vue) {
const routerPush = Router.prototype.push
const routerReplace = Router.prototype.push
const isNavigationDuplicated = (currentRoute, nextRoute) => {
const { name: nextName, params: nextParams = {}, query: nextQuery = {} } = nextRoute
const { name, params, query } = currentRoute
return equals(nextQuery, query) && equals(nextParams, params) && equals(nextName, name)
}
Router.prototype.push = function push(location) {
if (!isNavigationDuplicated(this.currentRoute, location)) {
return routerPush.call(this, location)
}
}
Router.prototype.replace = function replace(location) {
if (!isNavigationDuplicated(this.currentRoute, location)) {
return routerReplace.call(this, location)
}
}
Vue.use(Router)
}

I noticed that error comes up when I tried to replace url query parameter with same value.
I have select filters and url query string params are in sync with their values. It works well as long as you change to a new value. If value remains the same (for example coming back from history) and thus replacing query string parameter with same value, error pops out.
Solution was to check if value is changed, and then replace query param in router:
let newValue = 'foo'; // new query value for parameter
let qcopy = { ...this.$route.query }; // clone current query
// prevent NavigationDuplicated: Avoided redundant navigation to current location
if (qcopy['your_param'] != newValue){
qcopy['your_param'] = newValue;
this.$router.replace({query: qcopy});
}

Stop the click propagation from hitting the router action
I have a high-level answer here that may be useful to others. It may not directly answer the OP, but this thinking applies.
My take on this is to NOT fiddle around with global configuration or attempt to catch router logic. It doesn't make sense to pollute your app with exceptions all over the place.
In the case of a results view with filters. If the filters happen to be presented as router links (for various reasons), but we don't want actual router logic to occur when they're clicked.
So, capture the click before it gets to the router action!
Then you get the best of both worlds:
Search filters (as a link) that...
Do logic within a view
Still provide the benefit of being presented as a link
(benefits to bot scans, user convenience and accessibility, etc)
Technique:
Use #click.prevent on a child inside the router-link, to capture and stop the click from hitting the router.
Example:
Before:
Router logic occurs, even though we're in the route already
<router-link class="nav-link" :to="{name: 'MovieSection'}" >
<i class="fas fa-film"></i>Peliculas
</router-link>
After:
Router logic inhibited, we run other logic (applyFilter)
<router-link class="nav-link" :to="{name: 'MovieSection'}" >
<div #click.prevent="myFunc(something)">
<i class="fas fa-film"></i>Peliculas
</div>
</router-link>
With this approach you may avoid messy high-level exceptions in your app.

Best practice would be:
import { isNavigationFailure, NavigationFailureType } from 'vue-router/src/util/errors';
this.$router.push(...).catch((error) => {
if (!isNavigationFailure(error, NavigationFailureType.duplicated))
throw error
);
}
See the Vue Router docs

This worked for me in the router/index.js init. You can catch can handle the exception types or message you want in the call() method to ignore certain conditions or throw certain ones.
See
//HANDLE ROUTE ERRORS HERE
comment
import Vue from "vue";
import VueRouter from "vue-router";
import routes from "./routes";
Vue.use(VueRouter);
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Router instance.
*/
export default function(/* { store, ssrContext } */) {
const router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
// Leave these as they are and change in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: "history"
});
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
var token = window.localStorage.getItem("token");
if (token != null) {
var decodedtoken = JSON.parse(atob(token.split(".")[1]));
var role =
decodedtoken[
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
];
if (role != "Admin") {
next("/accessdenied");
} else {
next();
}
} else {
next("/login");
}
} else {
next();
}
if (to.meta.requiresLogin) {
var token = window.localStorage.getItem("token");
if (token != null) {
next();
} else {
next("/login");
}
}
});
//HANDLE ROUTE ERRORS HERE
const originalPush = router.push;
router.push = function push(location) {
return originalPush.call(this, location).catch(err => {
console.warn("router>index.js>push()", err);
});
};
return router;
}
Based on #Abhishek Shastri's answer

Here's is a simple and efficient solution:
if(from.fullPath === to.fullPath){
return
}

Related

How to change pagination variables in react

I am working on building pagination. I'm still working on my API to fetch posts based on pagination, but at the moment I am stuck on a problem with states.
In my main file (where the posts will be), my code looks like this:
// Import modules
import React from "react";
import axios from "axios";
import url from "url";
// Import components
import { Snippet } from "../Snippet";
import { CreatePost } from "./CreatePost";
import { Pagination } from "./Pagination";
// Import styles
import "./css/Content.css";
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
export class Content extends React.Component {
state = {
collections: [
{ title: "ReactJS", color: "red" },
{ title: "HTML", color: "cyan" },
{ title: "CSS", color: "pink" },
],
snippets: [],
limitSnippets: 3,
page: 0,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.getSnippets();
}
getSnippets = async () => {
try {
let data = await api
.get(
`/snippets/fetchAll?limitSnippets=${this.state.limitSnippets}&page=${this.state.page}`,
{
body: {
limitSnippets: 3,
page: 1,
},
}
)
.then(({ data }) => data);
this.setState({ snippets: data });
} catch (error) {
console.log(error);
}
};
updatePagination = (page) => {
this.state.page = page;
console.log(this.state.page);
};
render() {
return (
<div className="content">
<h1 className="content-header">Snippets</h1>
<CreatePost contentUpdater={this.getSnippets} />
<Pagination updatePagination={this.updatePagination} />
<div className="w-layout-grid grid">
{this.state.snippets.map((snippet, i) => {
return (
<Snippet
key={i}
title={snippet.title}
code={snippet.code}
updatedDate={snippet.updatedDate}
createdDate={snippet.createdDate}
language={snippet.language}
creator={snippet.creator}
collections={snippet.collections}
/>
);
})}
</div>
<Pagination />
</div>
);
}
}
export default Content;
In pagination file, my code looks like this:
export const Pagination = (props) => {
// States
const [page, setPage] = useState(0);
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
const handleLeft = (event) => {
event.preventDefault();
if (page > 0) {
setPage(page - 1);
props.updatePagination(page);
} else {
console.log("handleLeft(): page not > 0");
}
//props.updatePagination(page);
//}
};
const handleRight = (event) => {
event.preventDefault();
// page < fetchAllPages
setPage(page + 1);
props.updatePagination(page);
};
/*useEffect(() => {
props.updatePagination(page);
}, [page]);
*/
return (
<div className="paginate-div">
<div className="paginate-next">
<div className="paginate-next-icon" onClick={handleLeft}>
<i className="fas fa-caret-left"></i>
</div>
</div>
<a href="#" className="paginate-button first w-inline-block">
<div className="paginate-text">1</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">2</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">3</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">4</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">5</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">6</div>
</a>
<a href="#" className="paginate-button last w-inline-block">
<div className="paginate-text">...</div>
</a>
<div className="paginate-next" onClick={handleRight}>
<div className="paginate-next-icon">
<i className="fas fa-caret-right"></i>
</div>
</div>
</div>
);
};
I have my pagination component which is passed a prop that's a function to updatePagination(). The pagination component has functions for left and right button for switching thru pagination, and when it is clicked, the main file gets the pagination updated.
The problem I am having (sorry if it is confusing by the way i worded this)
The default page is 0 (which is basically page 1).
The crazy thing is when I press right (handleRight is called on submit), it stays at page 0, then if I click it again it goes to 1, then after if I press the left button (which called handleLeft on submit) while it is on page 1, it goes up to 2 somehow, but if I click it again it goes back down to 1, then if I click again it goes to 0.
Why is this strange problem occurring?
setPage is asynchronous, so when you setPage to decrement and then immediately call props.updatePage, props.updatePage is receiving the old value of page. You can read all about this common problem here.
const handleRight = (event) => {
event.preventDefault();
// Hey React, set page to page + 1 and rerender the component
setPage(page + 1);
// But before you do that, call props.updatePagination on the old value
props.updatePagination(page);
};
You should ask yourself, though, why you even store two stateful values of page at all (one in the parent component, one in the child component). Couldn't the Content component keep track of your page state (as it already does) and pass it down as a prop, getting rid of your need for a page state in the child component? This is called Lifting State Up, and it's a fundamental concept in React, which incentivizes you to use the least amount of state possible to avoid exactly this kind of desync. Furthermore, from the code you've shared, the Pagination component just displays the page numbers - why does it even need to be stateful at all?
The problem was that the old value of page was being used in updatePagination(), I fixed this by not running updatePagination(page) in the same place, I used useEffect(), and checked for changes to {page}, and ran updatePagination in there.
useEffect(() => {
props.updatePagination(page);
}, [page]);
The handleLeft, handleRight functions were changed to look like this:
const handleLeft = (event) => {
event.preventDefault();
let newPage = page - 1;
if (page > 0) {
setPage(newPage);
} else {
console.log("handleLeft(): page not > 0");
}
//}
};
NOTE"
In the comments section, someone made a point that I should not be storing the page number in two places, but rather store them in one place and pass it over as props. I have not currently tried to do this, but I plan on doing this soon. But for now, this is the best answer for this scenario.

Why does boostrap vue toaster dissapears immediately?

I am using Laravel + Vue.js to create a SPA. In the SPA, I am creating a form where user can write markdown content in it and click button to submit it. I want to show an error toaster at the bottom right corner of the screen if the user didn't input any content when they clicked the send button.
I am using boostrap vue toast to implement the error toaster.
However, when I clicked the send button, the error toaster will just blink for 1 second and dissapear immediately. Also, the error toaster blinks at top left corner which is different from what I wrote in the code below.
The mixin that contains the method to invoke the toast:
ErrorMessage.vue
# ErrorMessage.vue
<script>
export default {
methods: {
showErrorMessage (msg) {
this.$bvToast.toast(msg, {
title: ["Error!"],
variant: "danger",
toaster: "b-toaster-bottom-right"
});
}
}
};
</script>
I imported the above mixin in this vue component ArticleForm.vue.
ArticleForm.vue
<template>
<form #submit.prevent="passArticleData">
<div id="editor-markdown-editor">
<div id="editor-markdown">
<div
id="editor-markdown-tag-input"
#click="toggleTagModal"
>
<ul v-if="insertedTags.length">
<li v-for="tag in insertedTags"
:key="tag.id"
class="tag"
>
{{ tag.name }}
</li>
</ul>
<span v-else>タグを入力してください</span>
</div>
<div id="editor-markdown-textarea">
<textarea :value="input" #input="update" ref="textarea"></textarea>
<div id="drop-here"></div>
</div>
</div>
<div id="editor-preview">
<article v-html="compiledMarkdown(input)" class="markdown-render" ref="articleHtml"></article>
</div>
</div>
</form>
</template>
<script>
import _ from "lodash";
import ErrorMessage from "./mixins/ErrorMessage";
import { markdownTable } from "markdown-table";
export default {
props: {
article: Object,
tags: Array
},
mixins: [ErrorMessage],
data () {
return {
articleToPass: {
title: "",
body: "",
isDraft: true
},
input: "",
tagsToPass: [],
insertedTags: [],
tagModalOpen: false
};
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value;
}, 300),
passArticleData (e) {
// Get title from input
try {
this.articleToPass.title = this.$refs.articleHtml.getElementsByTagName("h1").item(0).innerHTML;
} catch (e) {
this.showErrorMessage(["Body md can't be blank"]);
}
// Remove first line(title) from users' input
this.articleToPass.body = this.input.substring(this.input.indexOf("\n") + 1);
// tag id of written article
const tagIds = this.insertedTags.map(obj => obj.id);
this.tagsToPass = tagIds;
this.$emit("handle-new-data", this.articleToPass, this.tagsToPass);
}
}
Parent component of the above vue component:
ArticleCreate.vue
<template>
<div id="content-area">
<header-component></header-component>
<main id="editor-area">
<article-form :article="article" :tags="tags" #handle-new-data="postArticle"></article-form>
</main>
</div>
</template>
<script>
import HeaderComponent from "./Header.vue";
import ArticleForm from "./ArticleForm.vue";
export default {
data () {
return {
article: {
title: "",
body: "",
is_draft: true
},
tags: []
};
},
components: {
HeaderComponent,
ArticleForm
},
methods: {
postArticle (articleObj, tagsData) {
const data = { article: articleObj, tags: tagsData };
axios.post("/api/articles", data)
.then((res) => {
this.$router.push({ name: "article.show", params: { article: res.data } });
});
}
}
};
</script>
I tried:
changed this.$bvToast to this.$root.$bvToast (based on this issue)
downgrade my bootstrap version from 4.6.0 to 4.5.3 (based on this question)
I have spent trying to solve this but failed. Does anyone know why is this happening and how can I make it work? Please let me know if you need any extra information. Thank you in advanced.
As stated in this comment on stackoverflow that is usually a sign of a bootstrap version mismatch.
I was actually able to reproduce that issue and also fix it with rolling back to bootstrap v4
Broken with bootstrap 5
https://codesandbox.io/s/bootstrap-vue-toasting-broken-with-bootstrap-5-bqe2c
Working with bootstrap 4
https://codesandbox.io/s/bootstrap-vue-toasting-working-with-bootstrap-4-jk2jl
I have figured out the problem.
The problem is that bootstrap-vue css is not loaded properly in my project.
Just add
import "bootstrap-vue/dist/bootstrap-vue.css";
to app.js and it works perfectly fine now.
Thank you

Access scoped slot from component

I got this component:
<template>
<Popover v-slot="{ open }">
<PopoverButton>
{{ title }}
</PopoverButton>
<div v-if="computedOpen">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
</Popover>
</template>
<script>
import {Popover, PopoverButton, PopoverPanel} from '#headlessui/vue'
import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
export default {
name: 'DropdownMenuButton',
mixins: [slots],
props: {
name: {
type: String,
},
title: {
type: String,
default: ''
},
},
components: {
Popover,
PopoverButton,
PopoverPanel,
ChevronDownIcon,
},
setup(props) {
const isOpen = ref(null);
const route = useRoute()
watch(route, () => {
isOpen.value = false;
});
const computedOpen = computed(() => {
let open = ...? //this is missing...
return isOpen.value && open.value;
});
return {
computedOpen
}
},
}
</script>
This component makes use of headless UI's popover.
Now I'd like to close the popover once the route changes. While the route-change is being detected fine, I can not access the <Popover>'s open value in the setup() method to determine, whether computedOpen should return true or false.
My question: How can I access v-slot="{ open } in the computed value?
What you want is not possible.
Think about it:
Everything inside <Popover> element (the slot content) is compiled by Vue to a function returning the virtual DOM representation of the content
This function is passed to the Popover component as slots.default and when the Popover component is rendering, it calls that function passing the open value as an argument
So open value is Popover component's internal state and is only accessible inside the template (ie. slot render function)
So I think your best bet is throw away the computed idea and just use the open value directly in the template or use method instead, passing open as an argument
<div v-if="isOpen && open">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
<div v-if="isPanelOpen(open)">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>

How to access my state of array in another router page VUEJS, VUEX

I made a page with two routes one is the home page another is the config where you can decide what should be written to that container, now in the config panel I was able to get the input values, I put them to my state with map actions now I am getting an array with string values in it.
How can I access that array with mapGetters ? I link my code:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{ message }}</h1>
</div>
</body>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
export default {
name: "Home",
data() {
return {
// message: this.store.state.message
elementVisible: true
};
},
computed: {
...mapGetters(["message", "sec"]),
...mapGetters({
message: "message",
sec: "sec"
}),
createdDate() {
return moment().format("DD-MM-YYYY ");
},
createdHours() {
return moment().format("HH:mm ");
}
},
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
setTimeout(() => (this.elementVisible = false), this.sec);
}
};
</script>
so what I have to do is to put to that{{message}} template my message which I received from the config panel and which is in my state right now sitting there as an array of string, for example, ["hello", "how are you"] that's how they are sitting there, so how can I grab the first one('hello') and write it out as a clean string and not as ["hello"] if you know how to grab all of them would be even better.
(RightNow it's just putting that whole array to my template)
Maybe I should something rewrite in my storejs file?
STOREJS:
const state = {
message: [],
// console.log(message);
sec: +[]
// other state
};
const getters = {
message: state => {
// console.log(this.state.message);
return state.message;
},
sec: state => {
return state.sec;
}
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
"SET_MESSAGE",
inputs.map(input => input.message)
);
return state.message;
},
setSec: ({ commit, state }, inputs) => {
commit("SET_TIMEOUT", inputs.map(x => x.sec).map(Number));
console.log(inputs.map(x => x.sec).map(Number));
return state.sec;
}
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
}
// other mutations
};
export default {
state,
getters,
actions,
mutations
};
what my homepage should do is that it writes out that message and there is a sec value which stands for the timeout, after that I want to continue with the second value in that array and when that times out I should want the third to be written out.. and so on.
Thank you!
Hello and welcome to Stack Overflow! Your message Array is being mapped correctly with mapGetters, but you're flattening it as a String when you put it inside the template with {{message}}, since the template interpolation logic covert objects to strings, the same as calling Array.toString in this case. You need to iterate it, i.e. using the v-for directive:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">
<span v-for="m of message" :key="m">{{m}}</span>
</h1>
</div>
</body>
</template>
Of course, if you only need the first item, you could show it directly using:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{message[0] || 'No message'}}</h1>
</div>
</body>
</template>

I'm trying to populate a second view with data by id from a router-link'd button in my first view. Not sure if I should go through the BaseURL

Is there a way to make this happen by id through the URL? And how do I set it up that the information that populates on the second page is only by that id number? Having a major braindead moment
<div v-for="prize in prizes" :key="prize.name">
<div class="card_individual">
<div class="card-media-container">
<img class="card_image" src="../assets/c5Cor.jpg" alt="people"/></div>
<div class="card-detail-container">
<div class="card_title">Win a {{ prize.name }}</div>
</div>
<div class="modal-footer">
<router-link :to="{ name: 'PriceDetail'}"><button type="button" class="btn btn-primary">{{ cards.btn_text }}</button></router-link>
</div>
</div>
</div>
<script>
import axios from 'axios'
export default {
name: 'Prizes',
data () {
return {
prizes: [],
}
},
mounted () {
this.getPrizes()
},
methods: {
getPrizes () {
axios.get('http://localhost:3000/prizes').then(response => {
this.prizes = response.data.prizes
this.id = response.data.prizes.id
})
}
}
}
You'll need to pass the id as a prop so that the 2nd view can use a different API call to retrieve a prize (price?) by id. The detail view should look something like this:
props: {
prizeId: Number
},
data () {
return {
prize: {},
}
},
mounted () {
this.getPrizeById()
},
methods: {
getPrizeById() {
axios.get('http://localhost:3000/prizebyid/' + this.prizeId).then(response => {
this.prize = response.data.prize
})
}
}
You can pass that id prop in the router-link from the main list like this:
<router-link :to="{ name: 'PriceDetail', params: { prizeId: prize.id }}">
This passes the id as part of the route's path (known as a param). Just make sure that the route definition for the PriceDetail route expects params, and also turns them into props (using props: true), which you can do like this:
{ path: '/<your-path>/:prizeId', name: 'PriceDetail', props: true }
And you need to make sure that your backend is configured to serve up individual data when a route like http://localhost:3000/prizebyid/1000 is visited.
If you want to make it more slick, you can serve up all results when visiting http://localhost:3000/prizes, and individual results when serving http://localhost:3000/prizes/1000, using the prizes route for both. I just used prizebyid route in my example for clarity since it might be easier to configure in the backend at first.

Categories

Resources