Cannot read property 'push' of undefined - vue and axios - vue.js

I have;
An API built from express running on port 2012
An Vue app running on port 8080
The Vue application communicates with the API using Axios.
I have been able to register users and log them in when the user clicks 'register' or 'login' it will submit their data to the API, if the API responses with an OK message, I use this.$router.push('/login') if a user successfully registered and this.$router.push('/dashboard') if a user is successfully logged in from the login page. However I continue to get "cannot read property 'push' of undefined" when I try to call this.$router.push on the dashboard vue.
login.vue (this.$router.push works)
<template>
<form id="login_form" method="post" v-on:submit.prevent="onSubmit">
<input type="text" name="username" class="form-control" v-model="auth.username" placeholder="username" />
<input type="password" name="password" class="form-control" v-model="auth.password" placeholder="password" />
<input type="submit" value="Submit" />
</form>
</template>
<script>
import Vue from 'vue'
import login_axios from '../axios/login_axios.js'
export default{
name: 'login_form',
data:function(){
return{
auth:{
username:'',
password:''
}
}
},
methods:{
onSubmit: login_axios.methods.onSubmit
},
components:{
login_axios
}
}
</script>
This login_vue component imports a javascript file called login_axios.js
login_axios contains a method called onSubmit which is called when the user clicks login/submit. onSubmit checks if res.data.auth.authenticated is true or false, if it is true, it executes this.$router.push to /dashboard, this works. However from the dashboard it does not work.
login_axios.js (this.$router.push works)
import Vue from 'vue'
import axios from 'axios'
import AxiosStorage from 'axios-storage'
let sessionCache = AxiosStorage.getCache('localStorage');
export default {
methods:{
async onSubmit(e){
e.preventDefault();
const res = await axios.post('http://myapi/login', this.auth);
try{
if(res.data.auth.authenticated){
sessionCache.put('authenticated', true);
this.$router.push('/dashboard');
}
} catch (error){
console.log(error);
}
}
}
}
Below is dashboard.vue which imports dashboard_axios.js
dashboard.vue (cannot read property 'push' of undefined)
<template>
<div>
<h1>Dashboard</h1>
Login
Register
Posts
About
</div>
</template>
<script>
import Vue from 'vue'
import dashboard_axios from '../axios/dashboard_axios.js'
export default {
name: 'dashboard',
methods:{
},
components:{
dashboard_axios
}
}
</script>
I have tried a few different things, but I have ended up setting self as a const of this. I defined the function verify_auth in dashboard_axios.js then called it directly after. I would expect this to work as it is just a function which should need called. I may be completely out of the loop as I am no expert at vue, but have been trying to research as much as I can.
dashboard_axios.js (cannot read property 'push' of undefined)
import Vue from 'vue'
import router from 'vue-router'
import axios from 'axios'
import AxiosStorage from 'axios-storage'
const self = this;
let sessionCache = AxiosStorage.getCache('localStorage');
sessionCache.put('authenticated', false);
console.log(sessionCache.get('authenticated'));
function verify_auth(){
if(sessionCache.get('authenticated')){
console.log('successfully verified authentication')
self.$router.push('/')
}else{
console.log('issue verifying authentication')
self.$router.push('/login')
}
}
verify_auth();
export default {
name: 'dashboard_axios',
methods:{
},
data: function() {
},
created: function(){
}
}

I am not 100% sure if this is the answer, but I have found a workaround.
I was importing javascript files such as 'dashboard_axios.js' which did not get loaded in as I wished it would. So instead, I renamed the file to 'dashboard_axios.vue' and added <template></template>, and left it empty, and then wrapped my js code in <script></script> then on the dashboard.vue I called the <dashboard_axios /> tag and it worked as I expected.

Related

How to use parameters and execute correctly a useLazyQuery from Apollo client in Vue 3?

I'm learning Apollo client with VueJs (Composition API syntax), I got a query that searches a user by the name field, and What I'm trying to do is to trigger the fetch with a #click event on a button next to an input in which you should write the user's name.
So basically I'm trying to use useLazyQuery and pass the name prop as param in the query but I don't know how to do it.
<div class="selectedUser">
<template>
<div class="container">
<input
type="text"
placeholder="Brian..."
v-model="userSearched"
/>
<p>{{userSearched}}</p>
<button
#click="load()">
Search for a user
</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, reactive, toRefs } from 'vue';
import { useLazyQuery, useQuery } from '#vue/apollo-composable'
import gql from 'graphql-tag';
export default defineComponent({
name: 'HomeView',
setup(){
const GET_USER_BY_NAME = gql`
query getUser($name: String!){
user(name: $name) {
id
name
age
username
nationality
}
},
`
const state = reactive({
userSearched : ''
});
const {
result: userSearhedResult,
load
} = useLazyQuery(GET_USER_BY_NAME);
` return {
users,
error,
loading,
load,
...toRefs(state)
}
}});
</script>
As you can see, my idea is/was to use the userSearched from the reactive() as param, but I'm not doing it right. Hope you share with me the correct way to do it. Thanks

Testing with vitest and testing-library is not working: it is due to using the SFC Script Setup?

I'm new to Vue and especially with the composition functions. I'm trying to test a component that uses the script setup; however, it seems that it is not working.
The component is this one:
<template>
<el-card class="box-card" body-style="padding: 38px; text-align: center;" v-loading="loading">
<h3>Login</h3>
<hr class="container--separator">
<el-form ref="formRef"
:model="form"
>
<el-form-item label="Username">
<el-input v-model="form.username" placeholder="Username"/>
</el-form-item>
<el-form-item label="Password">
<el-input type="password" v-model="form.password" placeholder="Password" />
</el-form-item>
<el-button color="#2274A5" v-on:click="submitForm()">Login</el-button>
</el-form>
</el-card>
</template>
<script lang="ts" setup>
import {reactive, ref} from 'vue'
import { useRouter } from 'vue-router'
import type {FormInstance} from 'element-plus'
import {useMainStore} from "../../stores/index"
import notification from "#/utils/notification"
import type User from "#/types/User"
const formRef = ref<FormInstance>()
const form: User = reactive({
username: "",
password: "",
})
const router = useRouter()
const loading = ref(false)
const submitForm = (async() => {
const store = useMainStore()
if (form.username === "") {
return notification("The username is empty, please fill the field")
}
if (form.password === "") {
return notification("The password is empty, please fill the field")
}
loading.value = true;
await store.fetchUser(form.username, form.password);
loading.value = false;
router.push({ name: "home" })
})
</script>
<style lang="sass" scoped>
#import "./LoginCard.scss"
</style>
When I try to test it:
import { test } from 'vitest'
import {render, fireEvent} from '#testing-library/vue'
import { useRouter } from 'vue-router'
import LoginCard from '../LoginCard/LoginCard.vue'
test('login works', async () => {
render(LoginCard)
})
I had more lines but just testing to render the component gives me this error.
TypeError: Cannot read properties of undefined (reading 'deep')
❯ Module.withDirectives node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:3720:17
❯ Proxy._sfc_render src/components/LoginCard/LoginCard.vue:53:32
51| loading.value = false;
52|
53| router.push({ name: "home" });
I tried to comment parts of the component to see if it was an issue with a specific line (the router for example), but the problem seems to continue.
I tried to search about it but I don't know what I'm doing wrong, it is related to the component itself? Should I change how I've done the component?
I had the same issue, and was finally able to figure it out. Maybe this will help you.
The problem was I had to register global plugins used by my component when calling the render function.
I was trying to test a component that used a directive registered by a global plugin. In my case, it was maska, and I used the directive in a input that was rendered somewhere deeply nested inside my component, like so:
<!-- a global directive my component used -->
<input v-maska="myMask" .../>
#vue/test-utils didn't recognize it automatically, which caused the issue. To solve it, I had to pass the used plugin in a configuration parameter of the render() function:
import Maska from 'maska';
render(MyComponent, {
global: {
plugins: [Maska]
}
})
Then, the issue was gone. You can find more info about render()
configuration here:
https://test-utils.vuejs.org/api/#global

Async loading child component doesn't trigger v-if

Hi everyone and sorry for the title, I'm not really sure of how to describe my problem. If you have a better title feel free to edit !
A little bit of context
I'm working on a little personal project to help me learn headless & micro-services. So I have an API made with Node.js & Express that works pretty well. I then have my front project which is a simple one-page vue app that use vuex store.
On my single page I have several components and I want to add on each of them a possibility that when you're logged in as an Administrator you can click on every component to edit them.
I made it works well on static elements :
For example, here the plus button is shown as expected.
However, just bellow this one I have some components, that are loaded once the data are received. And in those components, I also have those buttons, but they're not shown. However, there's no data in this one except the title but that part is working very well, already tested and in production. It's just the "admin buttons" part that is not working as I expect it to be :
Sometimes when I edit some codes and the webpack watcher deal with my changes I have the result that appears :
And that's what I expect once the data are loaded.
There is something that I don't understand here and so I can't deal with the problem. Maybe a watch is missing or something ?
So and the code ?
First of all, we have a mixin for "Auth" that isn't implemented yet so for now it's just this :
Auth.js
export default {
computed: {
IsAdmin() {
return true;
}
},
}
Then we have a first component :
LCSkills.js
<template>
<div class="skills-container">
<h2 v-if="skills">{{ $t('skills') }}</h2>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addModal" />
<LCModal ref="addModal"></LCModal>
<div class="skills" v-if="skills">
<LCSkillCategory
v-for="category in skills"
:key="category"
:category="category"
/>
</div>
</div>
</template>
<script>
import LCSkillCategory from './LCSkillCategory.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkills',
components: {
LCSkillCategory,
LCAdmin,
LCModal,
},
computed: mapState({
skills: (state) => state.career.skills,
}),
mixins: [Auth],
};
</script>
<style scoped>
...
</style>
This component load each skills category with the LCSkillCategory component when the data is present in the store.
LCSkillCategory.js
<template>
<div class="skillsCategory">
<h2 v-if="category">{{ name }}</h2>
<LCAdmin
v-if="IsAdmin && category"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
<div v-if="category">
<LCSkill
v-for="skill in category.skills"
:key="skill"
:skill="skill"
/>
</div>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addSkillModal" />
<LCModal ref="addSkillModal"></LCModal>
</div>
</template>
<script>
import LCSkill from './LCSkill.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkillCategory',
components: { LCSkill, LCAdmin, LCModal },
props: ['category'],
mixins: [Auth],
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.category,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
And so each category load a LCSkill component for each skill of this category.
<template>
<div class="skill-item">
<img :src="img(skill.icon.hash, 30, 30)" />
<p>{{ name }}</p>
<LCAdmin
v-if="IsAdmin"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
</div>
</template>
<script>
import LCImageRendering from '../../mixins/LCImageRendering';
import { mapState } from 'vuex';
import Auth from '../../mixins/Auth';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
export default {
name: 'LCSkill',
mixins: [LCImageRendering, Auth],
props: ['skill'],
components: { LCAdmin, LCModal },
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.skill,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
Then, the component with the button that is added everywhere :
LCAdmin.js
<template>
<div class="lc-admin">
<button v-if="addModal" #click="addModal.openModal()">
<i class="fas fa-plus"></i>
</button>
<button v-if="editModal" #click="editModal.openModal()">
<i class="fas fa-edit"></i>
</button>
<button v-if="deleteModal" #click="deleteModal.openModal()">
<i class="fas fa-trash"></i>
</button>
</div>
</template>
<script>
export default {
name: 'LCAdmin',
props: ['addModal', 'editModal', 'deleteModal'],
};
</script>
Again and I'm sorry it's not that I haven't look for a solution by myself, it's just that I don't know what to lookup for... And I'm also sorry for the very long post...
By the way, if you have some advice about how it is done and how I can improve it, feel free, Really. That how I can learn to do better !
EDIT :: ADDED The Store Code
Store Career Module
import { getCareer, getSkills } from '../../services/CareerService';
const state = () => {
// eslint-disable-next-line no-unused-labels
careerPath: [];
// eslint-disable-next-line no-unused-labels
skills: [];
};
const actions = {
async getCareerPath ({commit}) {
getCareer().then(response => {
commit('setCareerPath', response);
}).catch(err => console.log(err));
},
async getSkills ({commit}) {
getSkills().then(response => {
commit('setSkills', response);
}).catch(err => console.log(err));
}
};
const mutations = {
async setCareerPath(state, careerPath) {
state.careerPath = careerPath;
},
async setSkills(state, skills) {
state.skills = skills;
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Career Service
export async function getCareer() {
const response = await fetch('/api/career');
return await response.json();
}
export async function getSkills() {
const response = await fetch('/api/career/skill');
return await response.json();
}
Then App.vue, created() :
created() {
this.$store.dispatch('config/getConfigurations');
this.$store.dispatch('certs/getCerts');
this.$store.dispatch('career/getSkills');
this.$store.dispatch('projects/getProjects');
},
Clues
It seems that if I remove the v-if on the buttons of the LCAdmin, the button are shown as expected except that they all show even when I don't want them to. (If no modal are associated)
Which give me this result :
Problem is that refs are not reactive
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
See simple demo below...
const vm = new Vue({
el: "#app",
components: {
MyComponent: {
props: ['modalRef'],
template: `
<div>
Hi!
<button v-if="modalRef">Click!</button>
</div>`
}
},
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component :modal-ref="$refs.modal"></my-component>
<div ref="modal">I'm modal placeholder</div>
</div>
The solution is to not pass $ref as prop at all. Pass simple true/false (which button to display). And on click event, $emit the event to the parent and pass the name of the ref as string...

import and call function within Method not working

I am creating a small VueJs app, that calls the NASA image API and displays on the screen.
In the Header component (below) I have a search bar, when clicked this will call the Axios method defined in another file, I understood from the documents that if import functions to component then they need to be defined in the 'methods'. However, when I click search nothing displays on the console?
note: the call to NASA has been tested and does work when I include in the component. Which I guess begs the question if I should leave it in the component as I won't use it elsewhere.
but would still like to understand the logic behind the issue.
component code:
<template>
<div>
<h1>Nasa Image Search</h1>
<div class="search-container">
<form action="/action_page.php">
<input type="text" placeholder="Search.." name="search" />
<button v-on:click="search" type="submit">Search</button>
</form>
</div>
</div>
</template>
<script>
import nasa from '../apiCall'
export default {
name: 'Header',
methods: {
search : function(){
nasa
}
}
}
</script>
Axios function call:
import axios from 'axios'
const nasa = () => {
var url = `https://images-api.nasa.gov/search?q=apollo-13&media_type=image`
console.log(url) //bug testing
axios
.get(url)
.then(function(response) {
// handle success
console.log(response)
})
.catch(function(error) {
// handle error
console.error(error)
})
}
export default { nasa }
Your default export is actually an object with a function property, like this:
{
nasa: () => { ... }
}
When you import the object, you give it the name nasa, so you'd actually have to call the function like:
nasa.nasa()
Since you probably intend to just export the function, leave your import as is but change your export to:
export default nasa; // no brackets
And in your component, you don't need to embed that in a method, you can set it directly to the search method:
methods: {
search: nasa
}

Pass data from one component to all with $emit without using #click in VueJS

Trying to learn vuejs I got to the question how to pass any data from one component to all, using $emit but without using any #click.
It is possible some how that the data to be just available and grab it any time, without using the click?
Let's say we have this example with normal #click and $emit.
main.js
export const eventBus = new Vue()
Hello.vue
<template>
<div>
<h2>This is Hello component</h2>
<button
#click="emitGlobalClickEvent()">Click me</button>
</div>
</template>
<script>
import { eventBus } from '../main'
export default {
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
methods: {
emitGlobalClickEvent () {
eventBus.$emit('messageSelected', this.msg)
}
}
}
</script>
User.vue
<template>
<div>
<h2>This is User component</h2>
<user-one></user-one>
</div>
</template>
<script>
import { eventBus } from '../main'
import UserOne from './UserOne.vue'
export default {
created () {
eventBus.$on('messageSelected', msg => {
console.log(msg)
})
},
components: {
UserOne
}
}
</script>
UserOne.vue
<template>
<div>
<h3>We are in UserOne component</h3>
</div>
</template>
<script>
import { eventBus } from '../main'
export default {
created () {
eventBus.$on('messageSelected', msg => {
console.log('From UserOne message !!!')
})
}
}
</script>
I want to get this message : Welcome to Your Vue.js App from Hello.vue in all components, but without #click, if is possible.
You can create another Javascript file which holds an Object with your initial state. Similar to how you define data in your components.
In this file your export your Object and import it in all Components which need access to this shared state. Something along the lines of this:
import Store from 'store';
data() {
return {
store
}
}
This might help:
https://v2.vuejs.org/v2/guide/state-management.html
At this point if you app grows even more in complexity you might also start checking out Vuex which helps to keep track of changes(mutations) inside of your store.
The given example is essential a very oversimplified version of Vuex.