Fetching data from json server in vue with axios - vue.js

I would like to know is there is a way to do something like this without backend. I am calling all data from json server and displaying on home page:
async created() {
try{
const products = await axios.get('http://localhost:3000/products')
this.products = products.data
} catch(err) {
console.log(err)
}
}
Now when i click any of these products i would like to redirect user to new page and would like to display data of that specific object from json server.
What i have built for now is when user click on any product he gets redirected to route /product, and everything is hardcoded there, no dynamic data.
I hope my question is clear, thank you everybody.

You should consider using Vuex for this.
Move your method from created() to vuex's action and then call it in the hook.
The Vuex store's code is gonna be something like this:
state: {
products: []
},
getters: {
getProductById: (state) => (id) => state.products.find(product.id === id)
},
mutations: {
SET_PRODUCTS(state, products) {
state.products = products
}
},
actions: {
// this one is to call from the hook
fetchProducts({ commit }) {
return axios.get('http://localhost:3000/products').then(res => {
commit('SET_PRODUCTS', res.data)
})
}
}
Then call something like this from the component you're redirecting from:
<router-link
:to="{
name: 'Product', // im assuming this is the /product route's name
params: {
id: product.id // product.id is the id of your specific object your clicked on
}"
>
{{ product.productName }}
</router-link>
In your <Product /> component, you get the specific object by id from your Vuex store by using the getProductById() getter.
In your template:
...
<!-- the id we've passed as a route params -->
<h1>{{ getProductById($route.params.id) }}</h1>
...

Related

VUE3 pass data between files

In Home.vue I get data from db.json and store it into jobs: [] array.
export default {
name: 'Home',
data() {
return {
jobs: [],
}
},
components: {
},
mounted() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
}
}
Also in Home.vue I show this data, but only in a short list with:
v-for="job in jobs.slice(0, 5)"
In AllJobs.vue I want to show the full data from db.json and in AddJob.vue I will make a form to be able to add data to db.json.
In App.vue I have the router-links:
<template>
<div class="container">
<div class="navigation">
<h1 class="title">{{ $route.name }}</h1>
<nav>
<router-link :to="{ name: 'Latest open positions' }">Home</router-link>
<router-link :to="{ name: 'All open positions' }">Jobs</router-link>
<router-link :to="{ name: 'Add a new job' }">Dashboard</router-link>
</nav>
</div>
<router-view/>
</div>
</template>
How I pass data from Home.vue into AllJobs.vue?
Should I get another fetch method into AllJobs.vue to get data?
Should I get data into App.vue and then pass it into files that I need?
What is the best approach?
When it comes to handling API requests and sharing data between components, what you need is some state management solution like pinia.
You can fetch and save your data in a store and then use it in any component:
jobs.js
export const useJobsStore = defineStore('jobs', {
state: () => ({ jobs: [] }),
actions: {
fetchJobs() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
},
},
})
App.vue
import { mapActions } from 'pinia
import { useJobsStore } from './jobs.js'
export default {
methods: {
...mapActions(useJobsStore, ['fetchJobs'])
},
mounted() {
this.fetchJobs()
}
}
Home.vue and AllJobs.vue
import { mapState } from 'pinia'
import { useJobsStore } from './jobs.js'
export default {
computed: {
// this makes this.jobs available in script and template
...mapState(useJobsStore, ['jobs'])
}
}
One thing which is debatable is where to call fetchJobs action
In App.vue or main.js - this will fetch data as soon as you open the app, but can be unnecessary if the page you visit doesn't even use the data.
In each page that uses the data - solves the previous problem, but fetches the same data multiple times.
In each page that uses the data (with caching) - you can modify fetchJobs to make a request only if the data haven't been fetched already. This way the app will fetch the data as soon as you visit some page which uses it. And if you visit another page, it will use the cached value instead of making a request
There isn't a singe best approach, which one to pick depends on your needs

how can i get json object data in my url in vuejs

i have this json object
blog:{
"_id": "62567252370e769a8a93a517",
"title": "my trip to zanziar"
}
i'm trying to show in my url
http://localhost:8082/blog/my-trip-to-zanziar
in my url
but i'm getting
http://localhost:8082/blog/my%20trip%20to%20zanziar
and its not displaying any data
this is how i go to the route
<router-link :to="`/blog/${blog._title}`">
</router-link>
this is my router
{
path: "/blog/:title",
},
this is how i diplay to get the details under the title
mounted() {
axios
.get(`http://localhost:4000/api/blogs/${this.$route.params.title}`, {})
.then(response => {
console.log(response);
this.blog = response.data.blog;
})
.catch(error => {
console.log(error);
});
}
please how i can go about this
use String.prototype.replaceAll() where appropriate to replace empty spaces with hyphens:
<router-link :to="`/blog/${blog?._title?.replaceAll(' ', '-')}`" />
You might also consider making the formatted string it's own computed property if you need to perform the replacement more than once in your component.
you should add one more property like a slug in the blog object.
this.blog = { ...blog, slug: blog.title.replaceAll(' ','-').toLowerCase()}
now, you can use the slug in the router link.
<router-link :to="`/blog/${blog.slug}`"> ... </router-link>

How to retrieve and display a single record with Vue and axios

I have a basic CRUD rails 5.2 API with a Story model. I am building a Vuejs front end to consume it. Currently, The index view at /stories successfully pulls in data from the server. I can also add stories to the database via NewStory.vue at stories/new. I am trying now to show a single story on a page at stories/:id. The api server currently shows the single result I need at v1/stories/:id.
here is what I have at services/Api.js:
import axios from 'axios'
export default() => {
return axios.create({
baseURL: `http://localhost:3000/v1`
})
}
in StoriesService.js:
import Api from '#/services/Api'
export default {
fetchStories () {
return Api().get('stories')
},
addStory (params) {
return Api().post('stories', params)
},
getStory (params) {
return Api().get('/stories/1')
}
}
in ViewStory.vue:
<template>
<div class="stories">
<h1>Story</h1>
<div v-if="story" class="table-wrap">
<div>
<router-link v-bind:to="{ name: 'NewStory' }" class="">Add
Story</router-link>
</div>
<p>Title: {{story.attributes.title}}</p>
<p>Overview: {{story.attributes.description}}</p>
</div>
<div v-else>
The story with id:{{params}} does not exist <br><br>
<!-- <router-link v-bind:to="{ name: 'NewStory' }"
class="add_story_link">Add Story</router-link> -->
</div>
</div>
</template>
<script>
import StoriesService from '#/services/StoriesService'
export default {
name: 'story',
data () {
return {
title: '',
description: ''
}
},
mounted () {
this.getStory()
},
methods: {
async getStory (params) {
const response = await StoriesService.getStory(params)
this.story = response.data
console.log(this.story)
}
}
}
</script>
With the id of the record hard coded, in the Network tab, I see the request being made to the api and the correct record being retrieved.
However, if I change the getStory call to return Api().get('/stories/', params) I get a 304 response and can't retrieve data.
My question is how to get StoriesService.js to return localhost:3000/v1/stories/params.id, where params.id is the id of the story referenced in the url.
Currently you are not passing in any params to getStory, so you need to get them from the this.$route.params
async getStory () {
const response = await StoriesService.getStory(this.$route.params)
this.story = response.data
console.log(this.story)
}
Beside that axios only supports query string parameters so if your url looks like /stories/someId then you need to build it yourself in getStory:
getStory (params) {
return Api().get(`/stories/${params.id}`)
}
Additionally your data object is missing the story property:
data () {
return {
story: null,
title: '',
description: ''
}
},

VueJS router-link doesn't update component content

Category component
<ul v-else>
<li
v-for="cat in getCategories">
<router-link :to="{ name: 'ProductsCategory', params: {category_name: cat.name}}">{{ cat.name }}</router-link>
</li>
</ul>
This works fine and redirect fine to the correct link.
Problem is
When it redirect it doesn't call again the state while i am using vuex.
Component script
computed: {
getLoading () {
return this.$store.state.companyInfo.loading;
},
getCompanyBasicInfo () {
return this.$store.state.companyInfo.companyBasicInfo;
},
getCategories () {
return this.$store.state.categories.categoriesName;
},
getCategoriesLoading () {
return this.$store.state.categories.loadingState;
},
getCataegoryProducts () {
return this.$store.state.products.getCategoryProducts;
},
},
created() {
this.$store.dispatch('getCategories', this.$route.params);
this.$store.dispatch('getCategoryProductsAction', this.$route.params);
this.$store.dispatch('getCompanyBasicInfo', this.$route.params);
}
It should call getCategoryProductsAction which call my API and filter due to the router-link params.
This may be normal, because this component is not destroyed, but the $route parameters have changed.
So you can watch the $route for params.category_name changed
watch: {
// when redirect to new category_name, this will be callback
'$route': (new, old) => {
if (new.params.category_name !== old.params.category_name) {
// reload data, same with created()
}
}
}
see more: https://router.vuejs.org/guide/advanced/data-fetching.html#fetching-after-navigation
I would prefer to use the watch lifecycle in Vue.js.
Basically what is does is watching your route and when it changes you can tell it to run a function.
example:
watch: {
// call again the method if the route changes
'$route': 'initService'
}

Vuex accessing state BEFORE async action is complete

I'm having issues where a computed getter accesses the state before it is updated, thus rendering an old state. I've already tried a few things such as merging mutations with actions and changing state to many different values but the getter is still being called before the dispatch is finished.
Problem
State is accessed before async action (api call) is complete.
Code structure
Component A loads API data.
User clicks 1 of the data.
Component A dispatches clicked data (object) to component B.
Component B loads object received.
Note
The DOM renders fine. This is a CONSOLE ERROR. Vue is always watching for DOM changes and re-renders instantly. The console however picks up everything.
Goal
Prevent component B (which is only called AFTER component) from running its computed getter method before dispatch of component A is complete.
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
searchResult: {},
selected: null,
},
getters: {
searchResult: state => {
return state.searchResult;
},
selected: state => {
return state.selected;
},
},
mutations:{
search: (state, payload) => {
state.searchResult = payload;
},
selected: (state, payload) => {
state.selected = payload;
},
},
actions: {
search: ({commit}) => {
axios.get('http://api.tvmaze.com/search/shows?q=batman')
.then(response => {
commit('search', response.data);
}, error => {
console.log(error);
});
},
selected: ({commit}, payload) => {
commit('selected', payload);
},
},
});
SearchResult.vue
<template>
<div>
//looped
<router-link to="ShowDetails" #click.native="selected(Object)">
<p>{{Object}}</p>
</router-link>
</div>
</template>
<script>
export default {
methods: {
selected(show){
this.$store.dispatch('selected', show);
},
},
}
</script>
ShowDetails.vue
<template>
<div>
<p>{{Object.name}}</p>
<p>{{Object.genres}}</p>
</div>
</template>
<script>
export default {
computed:{
show(){
return this.$store.getters.selected;
},
},
}
</script>
This image shows that the computed method "show" in file 'ShowDetails' runs before the state is updated (which happens BEFORE the "show" computed method. Then, once it is updated, you can see the 2nd console "TEST" which is now actually populated with an object, a few ms after the first console "TEST".
Question
Vuex is all about state watching and management so how can I prevent this console error?
Thanks in advance.
store.dispatch can handle Promise returned by the triggered action handler and it also returns Promise. See Composing Actions.
You can setup your selected action to return a promise like this:
selected: ({commit}, payload) => {
return new Promise((resolve, reject) => {
commit('selected', payload);
});
}
Then in your SearchResults.vue instead of using a router-link use a button and perform programmatic navigation in the success callback of your selected action's promise like this:
<template>
<div>
//looped
<button #click.native="selected(Object)">
<p>{{Object}}</p>
</button>
</div>
</template>
<script>
export default {
methods: {
selected(show){
this.$store.dispatch('selected', show)
.then(() => {
this.$router.push('ShowDetails');
});
},
},
}
</script>
You can try to use v-if to avoid rendering template if it is no search results
v-if="$store.getters.searchResult"
Initialize your states.
As with all other Vue' data it is always better to initialize it at the start point, even with empty '' or [] but VueJS (not sure if Angular or React act the same, but I suppose similar) will behave much better having ALL OF YOUR VARIABLES initialized.
You can define initial empty value of your states in your store instance.
You will find that helpful not only here, but e.g. with forms validation as most of plugins will work ok with initialized data, but will not work properly with non-initialized data.
Hope it helps.