Make API call based on route parameter in Nuxt 3 dynamic compopnent - vue.js

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}`);

Related

After fetching api with axios it sometimes returns undefined under certain conditions

<template>
<div class="max-w-sm rounded overflow-hidden shadow-lg bg-white">
<div class="px-6 py-4">
<div class="font-bold text-xl mb-2">{{coin.data.name}} Price Statistics</div>
<table class="table-fixed">
<tbody>
<tr>
<th>Price</th>
<td>{{coin.data.market_data.current_price.usd}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { api } from '../services/api'
import { reactive } from 'vue'
const props = defineProps(['coinId'])
const coin = reactive({
data: []
});
async function fetchData() {
try {
coin.data = await api.get(`/coins/${props.coinId}`)
.then((response) => {
return response.data;
})
}
catch (e) {
console.error(e)
}
}
fetchData()
</script>
Hey guys, i'm struggling a lot trying to read data from an api. So, im reading this data from coingecko and when it's just on the first array it works very well. But when its an array inside an array its returns undefined when i try to refresh the page. But while i'm coding and it auto updates, it works very well. How can i treat it and make it so it doesn't show up undefined before anything?
Showing up an error
While coding and it just auto refreshes
This is probably caused by the data from the api not being received when you load the table. You can fix this by adding a loading variable, which you only set to true when the api call is finished. You can use v-if to toggle the element that uses the data so it only shows when the data is available and otherwise show 'loading...'. Like:
<template>
<div v-if="loading" class="max-w-sm rounded overflow-hidden shadow-lg bg-white">
<div class="px-6 py-4">
<div class="font-bold text-xl mb-2">{{coin.data.name}} Price Statistics</div>
<table class="table-fixed">
<tbody>
<tr>
<th>Price</th>
<td>{{coin.data.market_data.current_price.usd}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else-if="error !== ''">{{ error }}</div>
<div v-else>Loading...</div>
</template>
<script setup>
import { api } from '../services/api'
import { ref, reactive } from 'vue'
const props = defineProps(['coinId'])
const coin = reactive({
data: []
});
const loading = ref(false);
const error = ref('');
async function fetchData() {
try {
const response = await api.get(`/coins/${props.coinId}`);
/// here you could check if response is valid for you, otherwise throw an error
coin.data = response;
loading.value = false;
}
catch (e) {
error.value = e;
console.error(e)
}
}
fetchData()
</script>
You could also use the experimental Suspense component for this.
There is also another problem with your code: you use async/await and .then together. You should not do that, just pick one way or the other to deal with promises.
Hope this helps.

After making a POST request to my heroku endpoint and saved it to my mongoDB, the data only shows if I refresh the page

I deployed my Vue app to Netlify and the backend to heroku. Everything works fine, I can edit, delete and get data from my database, except when I submit the form (creating a new client) and I redirect to this.$router.push("/tabela"); . The data is created, but when I go to the ListComponent.vue (path:'tabela') my data isn't there. It only shows when I refresh the page. Before deploying to heroku, I fixed the issue with window.location.href="/tabela"; instead this.$router.push but now, If I use window.location.href="/tabela" I cannot save to my database anymore. I need to use this.$router.push in order to make it "work" but as I said, then I need to refresh the page to update my table with the new client.
Here is my app https://cadastro-app.netlify.app/ .
CreateComponent.vue
methods: {
submitForm(){
if(this.cliente.cpf === ''){
this.cliente.cpf = 'Não Informado'
}else if(this.cliente.cnpj === ''){
this.cliente.cnpj = 'Não Informado'
}
axios.post('https://cadastro-backend-app.herokuapp.com/clientes', {
data: this.cliente
}).then(function(){
this.cliente = {
nome: '',
sobrenome: '',
email: '',
telefone: '',
cnpj: '',
cpf: ''
}
}).catch(function (error) {
console.log(error);
});
/* window.location.href="/tabela"; */
this.$router.push("/tabela");
}
}
ListComponent.vue
<template>
<div class="row">
<div class="col-md-12 ">
<div class="search">
<input #keyup.enter.prevent="search()" v-model='nomePesquisado' class="form-control mb-3" type="text" placeholder="Pesquisar" aria-label="Search"/>
</div>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Nome completo</th>
<th>Email</th>
<th>telefone</th>
<th>CNPJ</th>
<th>CPF</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="cliente in clientes" :key="cliente._id">
<td>{{ cliente.nome }}</td>
<td>{{ cliente.email }}</td>
<td>{{ cliente.telefone }}</td>
<td>{{ cliente.cnpj }}</td>
<td>{{ cliente.cpf }}</td>
<td>
<router-link :to="{name: 'edit', params: { id: cliente._id }}" class="btn btn-success">Editar
</router-link>
<button #click.prevent="removerCliente(cliente._id)" class="btn btn-danger">Remover</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
clientes: [],
nomePesquisado:''
}
},
created() {
let apiURL = 'https://cadastro-backend-app.herokuapp.com/clientes/';
axios.get(apiURL).then(res => {
this.clientes = res.data;
console.log(this.clientes);
}).catch(error => {
console.log(error)
});
},
methods: {
removerCliente(id){
const apiURL = `https://cadastro-backend-app.herokuapp.com/clientes/${id}`;
const indexOfArrayItem = this.clientes.findIndex(i => i._id === id);
if (window.confirm("Tem certeza que deseja remover este item?")) {
axios.delete(apiURL).then(() => {
this.clientes.splice(indexOfArrayItem, 1);
}).catch(error => {
console.log(error)
});
}
},
search(){
this.$router.push(`results/${this.nomePesquisado}`);
}
}
}
</script>
router index.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: () => import("../components/CreateComponent")
},
{
path: "/tabela",
name: "tabela",
component: () => import("../components/ListComponent")
},
{
path: "/edit/:id",
name: "edit",
component: () => import("../components/EditComponent")
},
{
path: "/results/:id",
name: "results",
component: () => import("../components/Results")
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
You are changing the route (this.$router.push("/tabela");) too early
This is what is happening:
You make a POST request
Without waiting for a request to complete, you are telling Vue router to switch to ListComponent (this.$router.push("/tabela");)
Router activates ListComponent component
ListComponent runs a GET request to the server in it's created hook
Result is a "race". Will POST request be fast enough so the GET request sees the new data ?
To be sure, move this.$router.push("/tabela"); inside then

VueJS method returns promise. How to display data in template

VueJS front uses axios to retrieve data from Express API. Data returns as promise. I can't figure out how to display it in the template. The goal is to add this data to be displayed within a leaderboard, so I am doing a first query to get the username and the score(which is a sum) and then a second to get the data on the last single activity by each user.
Here is the code.
Leaderboard component:
<template>
<table class="ui celled table">
<thead>
<tr><th colspan="4">Leaderboard for {{ currentGroup.name }}</th></tr>
<tr>
<th>Username</th>
<th>Last Action</th>
<th>Date</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr v-for="(leader) in leaderboard" :key="leader.users_id">
<td>
<h4 class="ui image header">
<img v-if="leader.avatar_url" :src="leader.avatar_url" class="ui mini rounded image">
<v-gravatar v-else :email="leader.email" class="ui mini rounded image" />
<div class="content">
{{leader.username}}
</div>
</h4>
</td>
<td>{{ lastActByUser(leader.users_id).deed }}</td>
<td></td>
<!-- <td>{{ lastAct(leader.id).deed }}</td>
<td>{{ moment(lastAct(leader.id).created_at).strftime("%A, %d %b %Y %l:%M %p") }}</td> -->
<td>{{leader.count}}</td>
</tr>
</tbody>
</table>
</template>
<script>
import moment from 'moment-strftime'
import _ from 'lodash'
import ReportsService from '#/services/ReportsService'
import ActsService from '#/services/ActsService'
export default {
name: "Leaderboard",
data() {
return {
}
},
computed: {
leaderboard () {
return this.$store.getters.leaderboard;
},
currentGroup () {
return this.$store.getters.currentGroup;
}
// ,
// lastAct (userId) {
// return _.orderBy(this.actsByUser(userId), 'created_at')[0];
// }
},
mounted () {
this.getLeaderboard();
},
methods: {
getLeaderboard: async function () {
console.log('currentGroup: ', this.$store.getters.currentGroup)
this.$store.dispatch("updateLeaderboard", this.$store.getters.currentGroup);
},
moment: function (datetime) {
return moment(datetime);
}
,
async lastActByUser (leader_id) {
console.log('getting last act for user')
const response = await ActsService.fetchLastActByUser ({
userId: leader_id
});
this.deed = response.data.deed
this.created_at = response.data.created_at
console.log('lastAct response: ', response.data)
}
}
};
</script>
here is ActsService:
import Api from '#/services/Api'
export default {
fetchActs () {
return Api().get('acts')
},
...
fetchLastActByUser (params) {
console.log('calling the last act service with userId: ', params.userId)
return Api().post('acts/last_by_user', params)
},
}
here is the Express route:
const express = require('express');
const User = require('../models/User');
const auth = require('../middlewares/authenticate');
const Act = require('../models/Act');
let router = express.Router();
router.get('/', async (req, res) => {
const acts = await Act
.query().eager('user');
res.json(acts);
});
...
// GET last act for specified user
router.post('/last_by_user', async (req, res, next) => {
const lastAct = await Act
.query()
.findOne({
users_id: req.body.userId
});
console.log('lastAct on server side is: ', lastAct)
res.json(lastAct);
})
module.exports = router;

vue.js shows no error but page show blank page

im making a vue crud page, i already made the route, there is no error in the cmd but when i open the page i doesn't show anything.
here is the barang.vue file that i use for the home
<template>
<div>
<router-link to="/BarangForm/create">Tambah Barang</router-link>
<table>
<tr>
<th>no</th>
<th>Nama</th>
<th>jenis</th>
<th>Harga</th>
<th>Kuantitas</th>
</tr>
<tr v-for="(barang, i) in data_barang" :keys="i">
<td>{{ i + 1}}</td>
<td>{{ barang.nama_barang }}</td>
<td>{{ barang.harga_barang }}</td>
<td>{{ barang.jenis_barang }}</td>
<td>{{ barang.kuantitas }}</td>
<td>
<router-link :to="'/barang' + barang.id">Edit</router-link>
<button #click="deleteRow(barang.id)">Delete</button>
</td>
</tr>
</table>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'barang',
data () {
return{
data_barang: []
}
},
mounted () {
this.get()
},
methods: {
get () {
axios.get('barang').then(res => {
if (res.data.status === 'success') {
this.data_barang = res.data.result
}
})
},
deleteRow (id) {
axios.delete('barang/' + id).then(res => {
if(res.data.status === 'success') {
this.get()
}
})
}
}
}
</script>
here is the form for the create and edit
<template>
<div>
<form #submit.prevents="submitForm">
<div>
<label>Nama Barang : </label>
<input type="text" v-model="barang.nama_barang">
</div>
<div>
<label>Harga Barang : </label>
<input type="text" v-model="barang.harga_barang">
</div>
<div>
<label>Jenis Barang : </label>
<input type="text" v-model="barang.jenis_barang">
</div>
<div>
<label>Kuantitas : </label>
<input type="text" v-model="barang.kuantitas">
</div>
<button type="submit">Masukan</button>
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'BarangForm',
data () {
return {
barang: {
id: null,
nama_barang: null,
jenis_barang: '',
harga_barang: '',
kuantitas: ''
}
}
},
mounted () {
let id = this.$route.params.id
if (id) {
axios.get('/barang/' + id).then(res => {
this.barang = res.data.result
})
}
},
methods: {
submitForm () {
let data = this.barang
let url = 'barang'
if (this.barang.id){
url += '/' + this.barang.id
}
axios.post(url, data).then(res => {
if (res.data.status === 'success') {
this.$router.push('/barang')
}
})
}
}
}
</script>
this is the router for configuring the route
import Vue from 'vue'
import barang from '#/components/barang'
import BarangForm from '#/components/BarangForm'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/',
name: 'barang',
component : barang
},
{
path: '/barang/create',
name: 'BarangCreate',
component: BarangForm
},
{
path: '/barang/:id',
name: 'BarangEdit',
component: BarangForm
}
]
})
here is the file to render the page
import Vue from 'vue'
import App from './App.vue'
import Router from './router/index.js'
Vue.config.productionTip = false
new Vue({
Router,
render: h => h(App),
}).$mount('#app')
i plan to put every router-link in the home page.

Vue JS router query passing and bind to the axios

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