My use case is something like this.
User come to questionPapersTable.vue and choose a paper name and hit on a Start exam button
then the user is route to the startExam.vue component from there I want to pass that id parameter value to the axios as a parameter and want to display that id parameter in this StartExam.vue component.
I tried
ID comes is : {{$route.query.id}}
But this isn't display anything and even not give an error in console.
How do I achive this. This is my code.
questionPapersTable.vue
<template lang="html">
<div class="">
<h1>Hello</h1>
<table>
<tr>
<th>Paper Name</th>
<th></th>
</tr>
<tr v-for="n in 10">
<td>Demo</td>
<th><button #click="goStart(12)">Start exam</button></th>
</tr>
</table>
</div>
</template>
<script>
export default {
methods:{
goStart(paperId){
console.log("GO TO START EXAM!");
this.$router.push({
path:'/startExam',
params:{
id:paperId
}
});
}
}
}
</script>
startExam.vue
<template lang="html">
<div class="">
<h1>Start Exam</h1>
ID comes is : {{$route.query.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
created(){
axios.get('http://localhost/laravel_back/public/api/papers/' +id)
}
}
</script>
<style lang="css">
</style>
params are ignored if a path is provided:
router.push({ name: 'startExamName', params: { docId }}) // -> /startExam/123
router.push({ path: `/startExam/${docId}` }) // -> /startExam/123
// This will NOT work
router.push({ path: '/startExam', params: { docId }}) // -> /startExam
Within in the javascript code if you want to access the id,
this.$route.params.id
https://router.vuejs.org/en/essentials/navigation.html
Related
I'm attempting to create a simple Nuxt 3 app for learning purposes that uses dynamic routes to load data from an API when the page is loaded. What I'm trying to figure out is how to use the route id param with the composition API to call an external API and make the data available in the component.
So here is my basic folder structure:
/pages
\
index.vue
/currency
\
[id].vue
index.vue:
<template>
<main>
<h1>Index Page</h1>
<table border="1 px solid">
<thead>
<tr>
<th>Name</th>
<th>Symbol</th>
<th>Price</th>
<th>Details</th>
</tr>
<tr v-for="currency in data.data" :key="data.id">
<td>{{ currency.name }}</td>
<td>{{ currency.symbol }}</td>
<td>{{ currency.price_usd }}</td>
<td>
<NuxtLink :to="'/currency/' + currency.id">{{ currency.id }}</NuxtLink>
</td>
</tr>
</thead>
</table>
</main>
</template>
<script>
export default {
async setup() {
const {data} = await useFetch('/api/coinlore/tickers');
return {
data
};
}
}
</script>
and here is what I have for [id].vue
<template>
<main>
<h1>{{ data.data.name }} Detail page</h1>
{{ $route.params.id }}
</main>
</template>
<script>
export default {
async setup() {
const {data} = await useFetch('/api/coinlore/ticker?id=90');
console.log(data);
return {
data
};
}
}
</script>
Going from this blog post I tried this
<template>
<main>
<h1>{{ data.name }} Detail page</h1>
{{ $route.params.id }}
</main>
</template>
<script>
export default {
async setup() {
const coin = reactive({});
function fetchCoin(id) {
const {data} = await useFetch('/api/coinlore/ticker?id=' + $route.params.id);
coin = data;
}
watch('$route.params.id', fetchCoin)
return {
coin
};
}
}
</script>
but no dice there, either.
How can I simply 1) make my API call and 2) populate the data by using the id param in my [id].vue component?
Use the useRoute() hook:
import { useRoute } from 'vue-router';
export default {
setup() { 👇
const route = useRoute();
const { data: coin } = await useFetch('/api/coinlore/ticker?id=' + route.params.id);
return { coin }
}
}
demo
maybe it will help :_)
In index.vue it's better to use programatic routing:
<NuxtLink :to="{path: '/currency', params : {id: currency.id} }">{{ currency.id }}</NuxtLink>
// it will build link: `/currency/[id]`
and as posted by Tony19 there's a need to define route (useRoute hook) in the component:
// import in script
import { useRoute } from 'vue-router';
// define route from 'vue'
const route = useRoute()
// read ID from route params
const currencyId = route.params.id
// actually it's better use literal string for using dynamic data => better reading
const { data: coin } = await useFetch(`/api/coinlore/ticker?id=${currencyId}`);
I have the following Vue 3 component that I am using in 2 pages that are setup like this in vue.config.js:
module.exports = {
// Put this in the ASP.NET Core directory
outputDir: "../wwwroot/app",
pages: {
vehicleDetails: "src/VehicleDetails.js",
driverDetails: "src/DriverDetails.js"
}
};
Both of these pages work and share the same underlying 'Certificates' component that looks like this:
<template>
<div>
<h3 id="Vue_Certificates">Certificates</h3>
<div>
objectId: {{objectId}} test
<table class="table table-striped table-hover table-condensed2" style="clear: both;">
<thead>
<tr>
<th><b>test</b></th>
<th style="text-align: right;">
<a href="#" #click="addCertificate">
<i class="fa fa-plus-square"></i> Add
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="certificate in certificates" v-bind:key="certificate">
<td>{{ certificate.CertificateTypeId }}</td>
<td>
<a href="#" #click="removeCertificate(index)" title="Delete" style="float: right;" class="btn btn-default">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { onMounted, ref } from "vue";
import $ from 'jquery';
import _ from 'lodash';
export default {
components: {
},
props: {
objectId: String,
ownerIsVehicle: Boolean
},
data() {
return {
certificates: [],
types: []
}
},
created() {
const requestOne = axios.get("/api/v1.0/CertificateType/GetCertificateTypes");
const requestTwo = axios.get("/api/v1.0/Certificate/GetCertificateByObjectId", { params: { objectId: this.objectId, ownerIsVehicle: this.ownerIsVehicle } });
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
alert("Axios calls completed 1");
this.types = responseOne.data;
var mappedData = responseTwo.data.map(x => ({
...x,
ValidFrom: new Date(x.ValidFrom),
ValidTill: new Date(x.ValidTill)
}));
this.certificates = mappedData;
alert("Axios calls completed 2");
console.log("succes");
})).catch(errors => {
console.log("fail");
});
},
methods: {
removeCertificate(index) {
this.certificates.splice(index, 1);
},
addCertificate() {
alert("Hello?");
}
}
}
</script>
The above code does NOT render the Vue component, but once I take out the two cells containing the anchors that call addCertificate & removeCertificate it all suddenly works.
For some reason I don't get any error in Chrome dev tools .. npm console .. visual studio console .. nowhere, so I have no clue at all what is going wrong.
Important: If I take out 1 of the 2 lines in vue.config.js it works on the page that is still added. The purpose of this component is that it can be reused.
FYI: I shrank down the code as much as possible to make sure other code bits aren't the cause.
For the record, the data property "certificates" was first a ref([]) but that doesn't seem to help at all.
Thanks in advance!
So I am bad, really bad at asking questions so I decide to make a video so I can explain myself what I want to ask, in little words I can say I want to have more data available from a component to a child component, just like we do on a Ruby on Rails CRUD app in the view post page.
this is the video please look at the video and discard my words --> https://youtu.be/026x-UvzsWU
code:
row child component:
<template>
<tr>
<td class="name-logo-col">
<!-- dynamic routes for data -->
<router-link
:to="{ name: 'Companies', params: {id : row.Name} }">
<b>{{row.Name}}</b><br> ...
Sheet component:
<template>
<div class="container-fluid">
<div class="row">
<main role="main" class="col-md-12 ml-sm-auto col-lg-12 pt-3 px-4">
<div class=" pb-2 ma-3 ">
<h2 class="center">Funding</h2>
<v-divider></v-divider>
</div>
<div class="mb-5 table-responsive">
<!-- <v-data-table
:headers="headers"
:items="rows"
class="elevation-1">
</v-data-table> -->
<table class="table table-striped ">
<thead>
<tr>
<th>Name</th>
<th>Funding Date</th>
<th>Amount Raised</th>
<th>Round</th>
<th>Total Raised</th>
<th>Founder</th>
<th>Est.</th>
<th>Location</th>
<th>Lead Investor</th>
</tr>
</thead>
<tbody >
<Row v-bind:key="row.id" v-for="row in rows" v-bind:row="row" />
</tbody>...
//data
rows: [],
loading: true,
}
},
methods:{
async accessSpreadSheet() {
const doc = new GoogleSpreadsheet(process.env.VUE_APP_API_KEY);
await doc.useServiceAccountAuth(creds);
await doc.loadInfo();
const sheet = doc.sheetsByIndex[0];
const rows = await sheet.getRows({
offset: 1
})
this.rows = rows;
this.loading = false;
}
},
created() {
this.accessSpreadSheet();
// testing console.log(process.env.VUE_APP_API_KEY)
}
This where my view company dynamic router child component:
<template>
<v-container class="fill-height d-flex justify-center align-start" >
<h3> Company component: {{ $route.params.id }} </h3>
<p> {{$route.params.Location}} </p>
</v-container>
</template>
<script>
export default {
name: "Company",
data() {
return {
}
}
}
</script>
This is the parent component companies of company child :
<template>
<v-container class="fill-height d-flex justify-center align-start" >
<div>
<h1> Companies man view </h1>
<Company/>
<router-link to="/funds">
Back
</router-link>
</div>
</v-container>
</template>
<script>
import Company from '#/components/Company.vue'
export default {
name: "Companies",
components: {
Company
},
}
</script>
This is my router link index page related code:
{
path: '/companies/:id',
name: 'Companies',
props: true,
component: () => import(/* webpackChunkName: "add" */ '../views/Companies.vue')
}
]
const router = new VueRouter({
mode: 'history',
routes,
store
})
I'm not pretty sure if I understand the whole question, but I will try to answer the part that I understand.
If you want to pass the other elements inside the row rather than just the Name or Location to route with the name "Companies", your first code actually just needs a little change.
You just need to pass the whole row and don't forget to parse it into a string, so the data will remain after reload.
<router-link
:to="{ name: 'Companies', params: {id : JSON.stringify(row)} }">
and then parse it back into JSON inside the Companies component using JSON.parse($route.params.id).
Or
you can use multiple params, so instead of naming your params inside router index as path: '/companies/:id, you should name it as path: '/companies/:Name/:Location' and then you pass the data of the other params.
<router-link
:to="{ name: 'Companies', params: { Name: row.Name, Location: row.Location } }">
But all params are mandatory, so you have to pass value to all params every time.
OR
Rather than using params, you can use query, it was more flexible. You can pass any additional query without modifying the path inside router index.
<router-link
:to="{ name: 'Companies', query: { Name: row.Name,Location: row.Location } }">
and delete /:id from your router index, so it would be just path: '/companies',
and after that you can directly access the query value inside the Companies component with $route.query.Name
So I watched the video and I think you're approaching this from the wrong angle.
It seems that you're unfamiliar with Vuex and you might assume you pass all that data through $route, which is not what it's supposed to be used for.
So this would ideally be the flow:
You get data from API
Data gets stored in the Vuex store
User navigates to page
Param.id get retrieved from $route
Id gets send to Vuex store as a parameter in a getter
Getter returns data to component filtered by the id
Data gets used in component
Doing it this way keeps all your data centralized and re-usable in any component. You can even see in real-time what is in the Vuex store by going to Chrome tools and clicking the Vuex tab.
Let me know if you have further questions!
I have two other uses of v-for in separate components. They also sometimes throw errors. All three v-for invocations are wrapped with v-if/else. Here is the code that produces duplicate key errors & renders data twice:
AccountDashboard.vue
<tbody>
<tr v-if="!residents.length" class="table-info">
<td class="text-center">
<p>
No residents on record.
</p>
</td>
</tr>
<template v-else>
<tr is="AccountResidentList"
v-for="resident in residents"
v-bind:key="'resident-list-' + resident.id"
v-bind:first_name="resident.first_name"
v-bind:last_name="resident.last_name"
v-bind:dob="resident.dob | date_formatted"
>
</tr>
</template>
</tbody>
Note the unique id attempt in the binding of key.
Here is a look at the child component
ProviderAccountList.vue
<template>
<tr class="AccountResidentList">
<td>
{{ this.$attrs.id }}
</td>
<td>
{{ this.$attrs.first_name }} {{ this.$attrs.last_name }}
</td>
<td>
{{ this.$attrs.dob }}
</td>
<td>
<button #click="toResidentProfile({account_id, id})" class="btn btn-sm btn-purple btn-with-icon">
<div class="ht-25">
<span class="icon wd-25"><i class="fa fa-eye"></i></span>
<span class="pd-x-10">view</span>
</div>
</button>
</td>
<!--TODO: Add view profile button-->
</tr>
</template>
<script>
import Axios from "axios";
import router from "../../router";
import { mapGetters } from "vuex";
import moment from "moment";
export default {
name: "AccountResidentList",
computed: {
...mapGetters['Resident', {
resident: 'getResident'
}]
},
filters: {
date_formatted: (date) => {
return moment(date).format('MMMM Do, YYYY');
}
},
methods: {
toResidentProfile(account_id, resident_id) {
router.push(`/accounts/${account_id}/residents/${resident_id}`)
}
},
};
</script>
<style scoped></style>
My Axios call looks like:
Account.js (a namespaced vuex-module)
async retrieveAccount(context, account_id) {
// Axios.defaults.headers.common['Authorization'] = 'Bearer ' + window.$cookies.get('jwt')
let response
let valid_id = window.$cookies.get('valid_id');
response = await Axios.get(`http://localhost:3000/api/v1/providers/${valid_id}/accounts/${account_id}`, { headers: { 'Authorization': 'Bearer ' + window.$cookies.get('jwt') } })
.then((response) => {
let account = response.data.locals.account;
let account_address = response.data.locals.account_address;
let residents = response.data.locals.residents;
// set Account
context.dispatch('Account/setId', account.id, {root: true});
context.dispatch('Account/setProviderId', account.provider_id, {root: true});
.
.
.
// set AccountAddress
// !Array.isArray(array) || !array.length
if (account.address) {
context.dispatch('Account/setAddressId', account_address.id, {root: true});
context.dispatch('Address/setId', account_address.id, {root: true});
.
.
.
// set AccountResidents
// !Array.isArray(array) || !array.length
residents.forEach(resident => {
if (resident) {
// Add object to parent's list
context.dispatch('Account/setResidents', resident, {root: true}); // Set attr values for object
context.dispatch('Resident/setId', resident.id, {root: true});
.
.
.
(remaining attrs removed for brevity)
}
})
router.push(`/providers/${account.provider_id}/accounts/${account_id}`);
})
.catch(function(error) {
console.log(error);
})
Note: the Account action #setResidents simply calls the mutator that adds one resident to a list total.
i.e state.list.push(resident)
I logged the response to the console and can confirm that the data isn't being sent twice (or more) from my Axios call.
I have reviewed & attempted the following to no avail:
https://alligator.io/vuejs/iterating-v-for/
https://www.reddit.com/r/vuejs/comments/7n3zi4/vue_warn_duplicate_keys_detected_vfor_with/
https://github.com/hejianxian/vddl/issues/23
https://github.com/hejianxian/vddl#warning
https://medium.com/#chiatsai/vue-js-common-issue-duplicate-keys-stops-components-rendering-df415f31838e
Finally, It should be mentioned that I have tried variations of using/not using template to wrap the list, including/not including the for loop in the template, etc..
Did not anticipate it would be this bothersome to iterate a collection.
Am I overlooking something obvious?
Update: What worked for me
I needed access to the resident.id also the id declared in the paren seems like an index. So here is a look at what removed the duplicate render errors and allow me access to the resident's id even after fixing the duplicate keys error:
<template v-else>
<tr is="AccountResidentList"
v-for="(resident, id) in residents"
v-bind:key="id"
v-bind:id="resident.id"
v-bind:first_name="resident.first_name"
v-bind:last_name="resident.last_name"
v-bind:dob="resident.dob | date_formatted"
>
</tr>
</template>
Thanks again #Billal Begueradj for the assist!
For me, I suspect that in residents there are entries which have the same id. So we have to find out a way to overcome this issue. We can give it an efficient try as follows:
<tr
is="AccountResidentList"
v-for="(resident, id) in residents"
:key="id"
// rest of your code
In my project I've got such structure:
Client page which has sidebar, general client's info and router-view for children views.
routes:
{ path: '/clients/:id', component: Client,
children: [
{
path: '/',
component: ClientReview,
name: 'client-review'
},
{
path: 'balances',
component: ClientBalances,
name: 'client-balances'
},
{
path: 'report',
component: MainReport,
name: 'client-report'
},
Client's component (Client.vue):
<template>
<el-row>
<client-menu></client-menu>
<el-col class="client-view" :md="{ span: 22, offset: 2}" :sm="{ span: 20, offset: 4}" :xs="{ span: 18, offset: 6}">
<client-bar></client-bar>
<transition name="el-zoom-in-center">
<router-view></router-view>
</transition>
</el-col>
</el-row>
</template>
<script>
import ClientMenu from './ClientMenu.vue'
import ClientBar from './ClientBar.vue'
export default {
data () {
return {
loading: false,
};
},
components: {
'client-menu': ClientMenu,
'client-bar': ClientBar,
}
}
</script>
ClientBar component (ClientBar.vue):
<template>
<div class="client-bar">
<el-col :span="18">
<h3>{{ client.Name }}</h3>
<h4>{{ client.Address }}</h4>
</el-col>
<el-col :span="6" style="text-align: right;">
<el-button-group>
<el-button icon="edit" size="small"></el-button>
<el-button icon="share" size="small"></el-button>
</el-button-group>
</el-col>
<div class="clrfx"></div>
</div>
</template>
<script>
export default {
data () {
return {
client: {}
}
},
mounted () {
this.loadClient()
},
methods: {
loadClient: function() {
self = this;
this.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.client = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
}
}
</script>
And I've got ClientReview component, which is root component for clients/:id route and use the same api to load clients information as ClientBar:
<template>
<div>
<el-row v-loading.body="loading">
<el-col :span="12">
<table class="el-table striped">
<tr>
<td class="cell">Полное наименование</td>
<td class="cell">{{ clientInfo.FullName }}</td>
</tr>
<tr>
<td class="cell">УНП</td>
<td class="cell">{{ clientInfo.UNP }}</td>
</tr>
<tr>
<td class="cell">ОКЭД</td>
<td class="cell">{{ clientInfo.Branch.code }}<br>{{ clientInfo.Branch.name }}</td>
</tr>
<tr>
<td class="cell">Адрес</td>
<td class="cell">{{ clientInfo.Address }}</td>
</tr>
<tr>
<td class="cell">Аналитик</td>
<td class="cell">{{ clientInfo.Analytic.first_name }} {{ clientInfo.Analytic.last_name }}</td>
</tr>
<tr>
<td class="cell">Менеджер</td>
<td class="cell">{{ clientInfo.Manager.first_name }} {{ clientInfo.Manager.last_name }}</td>
</tr>
</table>
</el-col>
<el-col :span="12">
<classification-report></classification-report>
</el-col>
</el-row>
</div>
</template>
<script>
import ClassificationReport from '../reports/ClassificationReport.vue'
export default {
data () {
return {
loading: false,
clientInfo: {}
}
},
created () {
this.Client();
},
methods: {
Client: function() {
self = this;
self.loading = true;
self.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.clientInfo = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
},
components: {
'classification-report': ClassificationReport
}
}
</script>
The problem is when I load page client/:id first time or refresh the page client's data in ClientReview doesn't load.
The component is rendered (as I see it in Vue Devtools), and both requests to server are sent, but clientInfo object in ClientReview still empty.
Than if I go to balances or report page and after that go to client-review page everything is loaded.
Hope someone could help me.
It's because self happens to be another reference to the window object, and is available in all modules.
Let's walk thru the steps and see why this bug is happening.
You load up client:/id.
The created method in ClientReview does ajax request, and assigns self to this.
The mounted method in ClientBar does ajax request, and reassigns self to this. Note that this also changed the self variable reference in ClientReview.
The ClientReview ajax finishes and assigns self.clientInfo = response.data;. Since self is a reference to ClientBar, and ClientBar does not declare clientInfo as a root data property, this assignment does nothing.
ClientBar ajax finishes and assigns self.client = response.data;, which works.
You route away from ClientReview.
You route back to ClientReview. Repeat step 2.
ClientReview successfully renders because ClientBar has already rendered, and does not reassign self.
The answer is to do let self = this instead of self = this.
The lesson is ALWAYS DECLARE YOUR VARIABLES with var, let or const.