Rails API responding to request, but axios not retrieving data - vuejs2

I have a Rails 5.2.2 API that is serving JSON data at localhost:3000/v1/stories. I am trying to set up a Vuejs app to consume it with Axios. The rails terminal shows the request and throws no errors:
Started GET "/v1/stories" for 127.0.0.1 at 2018-12-19 18:06:44 -0500
Processing by V1::StoriesController#index as HTML
Story Load (0.8ms) SELECT "stories".* FROM "stories"
↳ app/controllers/v1/stories_controller.rb:6
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::JsonApi (6.25ms)
Completed 200 OK in 10ms (Views: 8.7ms | ActiveRecord: 0.8ms)
MyIn the vue app, I have the following relevant files:
Api.js retrieves the base connection:
import axios from 'axios'
export default() => {
return axios.create({
baseURL: `http://localhost:3000/v1`
headers: {
'Accept': 'applicaton/json',
'Content-Type': 'application/json'
}
})
}
StoriesService.js:
import Api from '#/services/Api'
export default {
fetchStories () {
return Api().get('/stories')
}
}
and in the view (components/Stories.vue):
<template>
<div class="stories">
<h1>Stories</h1>
<div v-if="stories" class="table-wrap">
<div v-for="story in stories" :key="story.id">
<h3>Title: {{story.title}}</h3>
<p>Summary: {{story.description}}</p>
</div>
</div>
<div v-else>
There are no stories... Let's add one now <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 {
data () {
return {
stories: []
}
},
mounted () {
this.getStories()
},
methods: {
async getStories () {
const response = await StoriesService.fetchStories()
this.stories = response.data.stories
}
}
}
</script>
In the Rails app, cors.rb is:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
methods: %i(get post put patch delete options head)
end
end
I made the suggested adjustment. here is the response from the GET request to the back end in the. It shows the data coming through, but for some reason, it does not display from the v-for loop.

Your Rails API endpoint is returning the stories array assigned to a data property, eg
{
"data":[...]
}
The response body object is assigned to response.data via Axios so what you actually want is
this.stories = response.data.data

Related

Fetch each object from API data using vue and axios

This is my script that calls axios and fetch data as posts
<script>
import axios from 'axios'
export default {
name: 'App',
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
},
data() {
return {
posts: null
}
},
};
</script>
My code on view that tries to fetch data as posts from the script above
<template>
<div id="app">
<ul>
<li v-for="post in posts" v-text="post.createdAt"></li>
</ul>
<div>
</template>
SAMPLE data fetched from API URL look like this
POSTS OBJECT VARIABLES
I am able to fetch API DATA in console log as an array but when I call one object from array which is createdAT, v-text = "post.createdAt" does not print/fetch list of createdAt date list.
Just solved it following this document USING AXIOS TO CONSUME API here is the link for that https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html .
Above code that I have posted works fine. Problem was on my API URL which was nested inside data[data[object]]. So the way I called data from that API
from this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
}
to this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data.data)
}
posts isn't reactive because the default value is null, make it an empty array, or use Vue.set instead:
Array:
posts: []
Vue.set:
.then(response => Vue.set(this, 'posts', response.data))
Edit
In response to the comments below:
You must import Vue from 'vue' to resolve the Vue is not defined error.
Regarding your v-for-key needing to be unique, you need to define a unique key on v-for elements. You can just use JSON.stringify(post):
<li v-for="post in posts" v-text="post.createdAt" :key="JSON.stringify(post)"></li>

Axios + Vue executing same POST twice

While executing a POST in Axios, the request is always sent twice. This is not a CORS issue, as I get two pairs of requests (CORS and POST, and then ANOTHER CORS and POST).
I scaffolded a sample application using the hello-world template and it still exhibits the same behaviour:
<template>
<div class="hello">
Test
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HelloWorld',
props: {
msg: String
},
mounted() {
axios.create({
baseURL: 'https://api.example.com'
}).post('/login', {
token: 'tokenString'
}).then((a) => {
console.log(a)
})
.catch((err) => {
console.log('err login')
console.log(err)
})
}
}
</script>
<style scoped>
</style>
I only get the console.log of the second request, but the network tab clearly shows the two pairs of requests, and the API also indicates that two POST requests (with exactly the same parameters) have been made...
This is actually a problem with an extension called Privacy Possum, so it has nothing to do with axios in particular.

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: ''
}
},

Vue app only renders API data correctly in local dev, not in production build (using vuex, axios, and parcel)

I've got a basic client-side rendered vue app that fetches a json array from an API endpoint and renders data from each array item as a list item in an ordered list.
The data renders as expected when I'm developing locally, using parcel index.pug to spin up my local dev environment. Here's a screenshot of the expected ordered list as shown in my local dev environment at localhost:1234:
However, the data does not render as expected when I build for production (using parcel build index.pug). Here's a screenshot of the unexpected behavior in production mode (which you can see live online at https://errands.netlify.com/):
Notice that the production version knows the fetched API data is an array with a length of 6 (because it renders out 6 empty <li>s), but for some reason the production version does not know the data inside each array object.
The full source code for this app is here, https://github.com/brianzelip/groceries-vue.
The main relevant code (the axios call to the API, which updates the vuex store, then renders out the ordered list) lives in TheGroceryForm.vue, which is also included here:
<template>
<form action="/submit" method="POST">
<ol class="list-reset border rounded">
<li class="item" v-for="item in allGroceryItems" v-bind:key="item._id">
<GroceryFormItemEditLink/>
<GroceryFormItemCheckboxInput :slug="item.slug"/>
<GroceryFormItemCheckboxLabel :slug="item.slug" :name="item.name"/>
<GroceryFormItemQtySelector :slug="item.slug"/>
<GroceryFormItemStoresSelector
:stores="item.stores"
:slug="item.slug"
:defaultStore="item.defaultStore"/>
</li>
</ol>
</form>
</template>
<script>
import axios from 'axios';
import GroceryFormItemEditLink from './GroceryFormItemEditLink.vue';
import GroceryFormItemCheckboxInput from './GroceryFormItemCheckboxInput.vue';
import GroceryFormItemCheckboxLabel from './GroceryFormItemCheckboxLabel.vue';
import GroceryFormItemQtySelector from './GroceryFormItemQtySelector.vue';
import GroceryFormItemStoresSelector from './GroceryFormItemStoresSelector.vue';
export default {
data() {
return {};
},
components: {
GroceryFormItemEditLink,
GroceryFormItemCheckboxInput,
GroceryFormItemCheckboxLabel,
GroceryFormItemQtySelector,
GroceryFormItemStoresSelector
},
computed: {
allGroceryItems() {
return this.$store.state.allGroceryItems;
},
allGroceryItemsCount() {
return this.$store.getters.allGroceryItemsCount;
}
},
getters: {},
created() {
axios
.get('https://groceries-vue-api.glitch.me/get')
.then(res => {
console.log('axio.get worked! res.data =', res.data);
console.log(`typeof res.data = ${typeof res.data}`);
const groceryData = res.data;
console.log('groceryData =', groceryData);
console.log(`typeof groceryData = ${typeof groceryData}`);
const newData = res.data.map(obj => obj);
console.log('newData', newData);
return newData;
})
.then(data => {
console.log('data from second then() =', data);
return data;
})
.then(data => (this.$store.state.allGroceryItems = data))
.catch(error => {
console.log('ERROR! ->', error);
});
}
};
</script>
Why does the data change when I build my app for production? And how can I change this behavior to get the expected outcome?
You should avoid using self closing tags.
https://github.com/vuejs/vue/issues/1036
So your TheGroceryForm.vue will be
<template>
<form action="/submit" method="POST">
<ol class="list-reset border rounded">
<li class="item" v-for="item in allGroceryItems" v-bind:key="item._id">
<GroceryFormItemEditLink></GroceryFormItemEditLink>
<GroceryFormItemCheckboxInput :slug="item.slug"></GroceryFormItemCheckboxInput>
<GroceryFormItemCheckboxLabel :slug="item.slug" :name="item.name"></GroceryFormItemCheckboxLabel>
<GroceryFormItemQtySelector :slug="item.slug"></GroceryFormItemQtySelector>
<GroceryFormItemStoresSelector
:stores="item.stores"
:slug="item.slug"
:defaultStore="item.defaultStore"></GroceryFormItemStoresSelector>
</li>
</ol>
</form>
</template>

adding headers to nuxt for making api rest call with axios

Inside my nuxt application i need to make call to an external api like this (file.vue) :
<template>
<div class="container">
<h1>{{ post.title }}</h1>
<pre>{{ post.body }}</pre>
<p><nuxt-link to="/posts">Back to the list</nuxt-link></p>
</div>
</template>
<script>
export default {
async asyncData({ app }) {
let { data } = await app.$axios.$get(`http://my.api.adress:8001/competition/sport/4?_format=json&limit=20&offset=0`)
return { post: data }
},
head() {
return {
title: this.post.title
}
}
}
</script>
To make this call works i need to pass tree arguments to my headers. Anyone has an idea on how to do that to make it work for all api call in nuxt ?
You can set headers using the axios module for nuxt (which you already do).
Taken from their documentation:
setHeader(name, value, scopes='common')
name: Name of the header
value: Value of the header
scopes: Send only on specific type of requests.
Examples:
// Adds header: `Authorization: 123` to all requests
this.$axios.setHeader('Authorization', '123')
// Overrides `Authorization` header with new value
this.$axios.setHeader('Authorization', '456')
// Adds header: `Content-Type: application/x-www-form-urlencoded` to only
// post requests
this.$axios.setHeader('Content-Type', 'application/x-www-form-urlencoded', [
'post'
])
// Removes default Content-Type header from `post` scope
this.$axios.setHeader('Content-Type', false, ['post'])
Please refer to the documentation for more information: https://github.com/nuxt-community/axios-module#setheadername-value-scopescommon