I'm developing a Promise-based modal component which provides the possibility of specifing a component as body of the modal itself. To achieve that result, I thought that a good solution would be using a dynamic component inside the modal template.
However, inside a NUXT application, if the component refers to the Vuex instance (this.$store), it turns out to be undefined (or better there is no $store object attribute). In the same way, any injection done inside plugins results undefined (e.g. inject('api', api) create the attribute $api, but it results undefined).
If I just use the component in the 'standard' way (e.g. placing it inside the page or another component template), everything works fine.
There should be some 'extra injection' that I should do before passing the component in a programmatic way.
Can anyone help me?
The NUXT project structure (simplified):
/pages/index.vue
/plugins/api.js
/store/auth.js
/components/HelloComponent.vue
/plugins/api.js
let api = {}
api.call = function (request, auth, unpack, axios = this.axios) {
if (!request) Error('backend.js:call invalid params:', request, auth, unpack, axios)
if (auth) {
if (request.headers)
request.headers['Authorization'] = 'Bearer ' + this.auth.accessToken
else
request.headers = { 'Authorization': 'Bearer ' + this.auth.accessToken }
}
return axios(request).then((response) => unpack ? response.data : response)
}
api.getAPI = function (api, params, auth = true, unpack = true) {
if (!api) Error('api.js:getAPI invalid params:', api)
console.log('api.js:getAPI api:', api)
return this.call({ method: 'get', url: api, params: params }, auth, unpack)
}
api.postAPI = function (api, params, data, auth = true, unpack = true) {
if (!api) Error('api.js:postAPI invalid params:', api, data)
console.log('api.js:postAPI api:', api)
return this.call({ method: 'post', url: api, params: params, data: data }, auth, unpack)
}
/*******************************************************/
/* NUXT plugin and reference injection */
/*******************************************************/
export default function (context, inject) {
console.log('[CALL] api.js')
/* assign global $axios instance */
api.axios = context.$axios
/* assign auth instance to access tokens */
api.auth = context.store.state.auth
/* inject backend reference into application instance */
inject('api', api)
}
/pages/index.vue
<template>
<div>
<span>
{{ $store.auth.state.name }} // -> Displays 'Chuck'
</span>
/* Object.keys(this).includes('$store): false'; Object.keys(this).includes('$auth): true' */
<component :is="cComponent" /> // -> this.$store is undefined; auth: undefined
<hello-component /> // -> Displays 'Chuck'; auth: Object {...}
</div>
</template>
<script>
import HelloComponent from '../components/HelloComponent.vue'
export default {
components: {
HelloComponent
},
created () {
this.$store.commit('auth/setName', 'Chuck')
},
computed: {
cComponent () {
return HelloComponent
}
}
}
</script>
/components/HelloComponent.vue
<template>
<span>
{{ $store.auth.state.name }}
</span>
</template>
<script>
export default {
created() {
console.log('auth:', this.$auth)
}
}
</script>
/store/auth.js
export const state = () => ({
accessToken: null,
refreshToken: null,
name: null,
})
export const mutations = {
setAccessToken(state, token) {
console.info('auth.js:setAccessToken', token)
state.accessToken = token
},
setRefreshToken(state, token) {
console.info('auth.js:setRefreshToken', token)
state.refreshToken = token
},
setName(state, name) {
console.info('auth.js:setName', name)
state.user = name
},
}
if you have no access of this pointer in Nuxt project, And you really need to access store, then simply use
window.$nuxt.$store instead of this.$store;
Hope it will solve your problem
Related
I'm fresh in Vue so this question can be dumb. I want to display data in Vue from my backend Rails API. The data should shows up each time a user enters the site. To do so I'm calling GET endpoint which is located below:
imports.js
const fetchSyncedProductsResultRequest = (self) => {
const jwtToken = self.$store.state.idToken;
return axios
.get(`/api/v1/imports/products/sync_result`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
}
})
.then(response => {
self.unsyncedProducts = response.data['products']
})
};
export {
fetchSyncedProductsResultRequest
};
Expected JSON response from this GET will be:
{:products=>
[{:id=>"611ea9a7392ab50013cf4713", :name=>"2-Tone Hoodie", :code=>"SS22CH013", :result=>nil, :last_sync_at=>nil},
{:id=>"60ec84062f25d400150b351c", :name=>"5-Pocket Denim", :code=>"SS22WP014", :result=>nil, :last_sync_at=>nil},
{:id=>"61966dc83e81dd001731ccd7", :name=>"Zip Shirt Jacket", :code=>"FW22WT001", :result=>nil, :last_sync_at=>nil},
{:id=>"61d5cab6b41408001b0e9376", :name=>"Yankees Satin Varsity Jacket", :code=>"FW22WJ021", :result=>nil, :last_sync_at=>nil}]}
Inside my Vue file I've got:
sync_products.vue
<template>
<div>
<div class="panel">
<div class="panel-body">
<h4>Synchronize products</h4>
<div v-for="product in fetchedProductSyncStatus" :key="product" class="status">
{{product}}
</div>
</div>
</div>
</div>
</template>
<script>
import {
fetchUnsyncedProductsRequest,
} from '../../api/imports'
export default {
name: 'BackboneSyncProducts',
data(){
return{
fetchedProductSyncStatus: []
}
},
}
</script>
<style scoped>
</style>
Looks like request is not sent because nothing shows up and I don't see it in Network tab. What determines the sending of this request?
You need to hook the fetchUnsyncedProductsRequest function to Vue's lifecycles, like created or mounted. See: https://vuejs.org/guide/essentials/lifecycle.html
I would also change the function to just return the data.
const fetchSyncedProductsResultRequest = (token) => {
return axios
.get(`/api/v1/imports/products/sync_result`, {
headers: {
Authorization: `Bearer ${token}`,
}
})
.then(response => {
return response.data['products']
})
};
export {
fetchSyncedProductsResultRequest
};
Then add the created hook and add the response to fetchedProductSyncStatus
export default {
name: 'BackboneSyncProducts',
data() {
return{
fetchedProductSyncStatus: []
}
},
created () {
const jwtToken = this.$store.state.idToken;
fetchUnsyncedProductsRequest(jwtToken).then(data => {
this.fetchedProductSyncStatus = data
})
}
}
Edit: Fixed the self reference error you commented about. On that note it is bad practice to store token in the client like a store
I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)
I wondered if passing a custom action with a custom fetch and type (which is not update) to startUndoable is feasible.
Or is it possible that somehow define a pattern with values in meta and based on this pattern the view would be re-rendered?
In this case the IMPORT is updating only one property in the database with a fixed value.
This is the action:
export const importParcel = ({ id }) => ({
type: IMPORT_PARCEL,
payload: {
id
},
meta: {
resource: 'parcels',
fetch: IMPORT,
refresh: true,
onSuccess: {
notification: {
body: 'Parcel Imported',
level: 'info'
}
},
onFailure: {
notification: {
body: 'Error: Import failed',
level: 'warning'
}
}
}
});
This is the handler:
fetchUtils
.fetchJson(`/${resource}/import/${params.id}`, {
method: 'PUT',
headers: getAuthenticationHeaders()
})
.then(res => ({ data: res.json }));
Thanks for your help! :)
Sure, as explained in the Optimistic Rendering and Undo documentation you can create whatever action you want with startUndoable:
import { startUndoable as startUndoableAction } from 'ra-core';
class App extends Component {
handleImport = () => {
this.props.startUndoable(importParcel());
};
render() {
return <Button onClick={this.handleImport}>Import Parcel</Button>;
}
}
export default connect(null, { startUndoable: startUndoableAction })(App);
You action must have a onSuccess notification in order to display the undo button.
The rest should be implemented in your data provider.
I'm having a hard time passing Data (in this case userInfo-Token) from inside a beforeEach(to, from, next) Vue-Router middleware to the coressponding Vue-Component. I'm working on Vue-SinglePage components files as following:
App.js (entry Point)
<template>
<div id="app">
<Navigation/>
<router-view/>
<Footer/>
</div>
</template>
Router.js (router-view)
routes: [
{
path: '/',
meta: {requiresAuth: true}
}
]
router.beforeEach((to, from, next) => {
if(to.meta.requiresAuth){
// getting token form local storage
const token = localStorage.getItem('id_token');
// retrieving User Information
axios.defaults.headers.common['Authorization'] = "Bearer "+ token;
axios.post('http://localhost:4000/auth').then((result) => {
next(); // <-- how will the following component be able to work with the result?
}
}
}
Dashboard.js ('./' Component)
export default {
computed: {
welcomMsg: () => 'Hello ' + result.userName
}
}
What i did so far:
I tried a passing the userInfo from Entry-Point -> Router -> Component as properties. However it didnt work due to the information being asynchronos.
I tried to attach the data inside the beforeEach to the meta-Object. But I find myself unable to access the meta-Object inside the Component.
Maybe my approach is totally wrong. In this case: Is there a better way to pass the recieved UserData to Vue Components and make them avaliable there?
Thank you in advance.
I would handle this by using Vuex, which is a state management library.
You'd need to use create a Vuex module and add it to your root Vue instance like so:
const store = new Vuex.Store({
state: { userData: {} },
mutations: {
SET_USER_DATA: (state, data) => state.userData = data,
}
});
new Vue({ // your root Vue instance
el: '#app', // or whatever you've bound to root instance to
store, // the reference to the Vuex store
... // the rest of the root Vue definition
})
Now, the root Vue instance and all of its internal components will have a reference to the Vuex store via the $store property.
Then you can set the userData in you axios callback by calling store.commit, which will subsequently call the specified mutation in the Vuex Store (this would require that you also have the reference to the Vuex store object in this scope):
axios.post('http://localhost:4000/auth').then((result) => {
store.commit('SET_USER_DATA', result);
next();
});
And you can access the userData from any component via the $store object:
export default {
computed: {
welcomeMsg() {
let { userName } = this.$store.state.userData || {};
return (userName) ? 'Hello ' + userName : '';
}
}
}
notice I also updated the welcomeMsg computed to fix the spelling and to not use an arrow function
I'm trying to learn VueJS and it's going well but i run into one problem where i can't get multiple components to work on one page, for some reason the html will load but everything in my export default wont work.
So i have 2 components: a ShortenerComponent and a StatsComponent the ShortenerComponent works and does everything it should, but the StatsComponent will only load the html and will not do anything inside the export default this is my code:
StatsComponent.vue (the ShortenerComponent is the same except for the methods and html.):
<script>
// get the csrf token from the meta
var csrf_token = $('meta[name="csrf-token"]').attr('content');
export default {
data() {
return {
};
},
test() {
this.getStats();
},
methods: {
// get all the existing urls
getStats() {
console.log('console log something');
axios.get('urls').then((response) => {
console.log('console log something');
});
},
}
}
My shortenercomponent:
<script>
// get the csrf token from the meta
var csrf_token = $('meta[name="csrf-token"]').attr('content');
export default {
data() {
return {
shortUrl: '',
url: '',
error: '',
};
},
methods: {
createUrl() {
axios({
method: 'post',
url: '/',
data: {
_token: csrf_token,
url: this.url
},
}).then(response => {
console.log(response);
this.shortUrl = response.data.hash;
}).catch(error => {
this.error = error.response.data.message;
});
}
}
}
and last but not least my app.js file
Vue.component('shortener',require('./components/ShortenerComponent.vue'));
Vue.component('stats', require('./components/StatsComponent.vue'));
var vue = new Vue({
el: '#app',
});
I hope someone could figure out what i did wrong :D
So in my code i had the test method, i thought that would call the getStats method. What i did not now is that Vue has the created method used to run code after an instance is created.
So what i had to do was:
function created()
{
this.getStats();
}
Source: Vue instance Lifecycle Hooks