Ember.js test page keeps refreshing automatically - javascript

I'm trying to follow an example integration test from here: https://guides.emberjs.com/release/testing/testing-components/ (Testing Actions)
My problem is that the Test output keeps refreshing automatically, perpetually, for some reason?
Test code:
test('Can handle submit action', async function (assert) {
/*
* THIS TEST HAS PROBLEMS
* THE PAGE CONSTANTLY REFRESHES FOR THIS TEST, NO IDEA WHY, NEED TO INVESTIGATE
*/
assert.expect(1);
// test double for the external action
this.set('externalAction', (actual) => {
const expected = {inputValue: 'test'};
assert.deepEqual(actual, expected, 'submitted value is passed to external action');
});
await render(hbs`{{user-form inputValue="test" saveAction=(action externalAction)}}`);
// click the button to submit the form
await click('#submitButton');
});
Component.js:
import Component from '#ember/component';
import {computed} from '#ember/object';
export default Component.extend({
inputValue: '',
submitText: 'Save',
inputIsValid: computed('inputValue', function () {
return this.inputValue.length > 3;
}),
actions: {
save(inputValue) {
if (this.inputIsValid) {
this.saveAction(inputValue); // pass action handling to route that uses component
}
}
}
});
component template:
<br>
<br>
<form onsubmit={{action "save" inputValue}}>
{{#unless inputIsValid}}
<div style="color: red" class='validationMessage'>
Hey it is not valid!
</div>
{{/unless}}
<label id="inputLabel">{{inputLabel}}</label>
{{input type="text" id="input" placeholder=inputPlaceholder value=inputValue class="form-control"}}
<br>
<button type="submit" id="submitButton" class="btn btn-primary">{{submitText}}</button>
</form>
{{outlet}}
I thought it might be because the form in the template keeps submitting, but that can't be the case since it should only click submit once. Any help much appreciated!

Following #Lux's suggestion written as comment; you need to do the following to prevent the form submission from reloading the page:
save(inputValue, event) {
event.preventDefault()
if (this.inputIsValid) {
this.saveAction(inputValue); // pass action handling to route that uses component
}
}
You receive the event as the last argument and call preventDefault tells the browser to not to handle the event as it would normally. See MDN for a better explanation.

Related

Vue - Keep input focused on route change

I have a TopNavBar component, that is present on every route. This component includes a search input field. When a user clicks on the input field the route changes from /bar to /foo but input focus is lost. How can I (re)focus on the input?
TopNavBar.vue
<template>
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
</template>
<script setup>
const searchInput = ref(null);
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
function initSearch() {
if (router.currentRoute.value.name != "/foo") {
router.push({ path: "/foo", query: { initSearch: true }, key: route.fullPath });
}
}
watch(
() => router.currentRoute.value.path,
(newRoute) => {
if (newRoute == "/foo") {
searchInput.value.focus();
}
}
);
</script>
I'm using Vue3 and Nuxt3. v-focusz directive is declared globally in /plugins` folder and works as expected.
Update
TopNavBar is inside Nuxt 3 layout. Also, upon further investigation I've realised that the input does focus on route change but immediately loses it again.
You can achieve this by using $refs, Attach a reference on input element and then call focus method on it.
In template:
<parent-component>
<search-component ref="searchComponentRef" />
</parent-component>
In script:
mounted() {
this.$refs.searchComponentRef.$el.focus();
}

login page with local storage in vue.js

I want to create a login page which calls the sample login API(https://reqres.in/).
I want after user logged in it go to another page and users stay logged in if the page is
refreshed but after i click the login button it doesn't go to another page and just login page refresh! and local storage is empty.
thank you for your help!
this is my login form:
<template>
<form>
<label>Email:</label>
<input required v-model="email" />
<label>Password:</label>
<input type="password" required v-model="password" />
<button #click="login">LOGIN</button>
</form>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
email: "",
password: "",
};
},
methods: {
async login() {
let result = await axios.get(
`https://reqres.in/api/users?email=${this.email}&password=${this.password}`
);
if (result.status == 200 && result.data.length > 0) {
localStorage.setItem("userInfo", JSON.stringify(result.data));
this.$router.push("/firstPage");
}
console.log(result)
},
},
};
</script>
When a <form> contains a single <button>, it's treated as the form's submit button, causing the submit event, which reloads the page.
To solve this, you can invoke Event.preventDefault on the form's submit-event with the v-on:submit directive along with the .prevent event modifier:
<form #submit.prevent>
⋮
</form>
demo
Try to use Named Routes: https://router.vuejs.org/guide/essentials/named-routes.html
There is a full example here: https://github.com/vuejs/vue-router/blob/dev/examples/named-routes/app.js

Vue2: Use form component with input type textarea to display AND edit data (without directly manipulating props)

I am building an MVP and this is the first time I do web development. I am using Vue2 and Firebase and so far, things go well.
However, I ran into a problem I cannot solve alone. I have an idea how it SHOULD work but cannot write it into code and hope you guys can help untangle my mind. By now I am incredibly confused and increasingly frustrated :D
So lets see what I got:
Child Component
I have built a child component which is a form with three text-areas. To keep it simple, only one is included it my code snippets.
<template>
<div class="wrap">
<form class="form">
<p class="label">Headline</p>
<textarea rows="2"
v-model="propHeadline"
:readonly="readonly">
</textarea>
// To switch between read and edit
<button
v-if="readonly"
#click.prevent="togglemode()">
edit
</button>
<button
v-else
type="submit"
#click.prevent="togglemode(), updatePost()"
>
save
</button>
</form>
</div>
</template>
<script>
export default {
name: 'PostComponent'
data() {
return {
readonly: true
}
},
props: {
propHeadline: {
type: String,
required: true
}
},
methods: {
togglemode() {
if (this.readonly) {
this.readonly = false
} else {
this.readonly = true
}
},
updatePost() {
// updates it to the API - that works
}
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
And my parent component:
<template>
<div class="wrap">
<PostComponent
v-for="post in posts"
:key="post.id"
:knugHeadline="post.headline"
/>
</div>
</template>
<script>
import PostComponent from '#/components/PostComponent.vue'
export default {
components: { PostComponent },
data() {
return {
posts: []
}
},
created() {
// Gets all posts from DB and pushes them in array "posts"
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Current Status
So far, everything works. I can display all posts and when clicking on "edit" I can make changes and save them. Everything gets updated to Firebase - great!
Problem / Error Message
I get the following error message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
As the error says I should use a computed property based on the props value. But how can I achieve that?
Solution Approach
I believe I have to use a computed getter to return the prop value - how to do that?
And then I have to use the setter to emit an event to the parent to update the value so the prop passes it back down - how to do that?
I have found bits and pieces online but by now all I see is happy families passing around small packages of data...
Would be really thankful for a suggestion on how to solve this one! :)
Thanks a lot!
This error shows because of your v-model on texterea which mutate the prop, but in vue it is illegal to mutate props :
<textarea rows="2"
v-model="propHeadline"
:readonly="readonly">
</textarea>
So, what you could do is to use this created() lifecycle hook and set the propHeadline prop as data :
<script>
export default {
name: 'PostComponent'
data() {
return {
readonly: true,
headline: ""
}
},
props: {
propHeadline: {
type: String,
required: true
}
},
created() {
this.headline = this.propHeadline
}
}
</script>
An then, update the new variable on your textarea :
<textarea rows="2"
v-model="headline"
:readonly="readonly">
</textarea>

Vue JS change submit button if errors

Using Vuex I have a form that when the button is clicked (#click="loader(true)") sends to the loader mutation to change loading to true, which then sets a is-loading class with Bulma CSS to true ('is-loading' : $store.state.index.loading ).
I then receive errors from the server if the form is empty with errors.title, this works fine with the inputs but how do I then set the is-loading class to false if there are errors?
(the code snippet will not work if you run it)
export const state = () => ({
loading: false
});
export const mutations = {
loader(state, value) {
state.loading = value;
}
}
<form #submit.prevent="postThis">
<div class="field">
<div class="control">
<input class="input" :class="{ 'is-danger': errors.title }" type="text" id="title" placeholder="I have this idea to..." autofocus="" v-model="newTitle">
</div>
<p class="is-size-6 help is-danger" v-if="errors.title">
{{ errors.title[0] }}
</p>
</div>
<div class="field">
<div class="control">
<button #click="loader(true)" type="submit" :class="{'is-loading' : $store.state.index.loading }">
Post
</button>
</div>
</div>
</form>
<script>
import {mapMutations,} from 'vuex';
methods: {
...mapMutations({
loader: 'index/loader'
})
}
</script>
The question is about using ...mapMutations, but in case someone want to add business logic, mapAction and mapState would be recommended. I will explain how to make it work with mapAction and mapState since calling API might involve using business logic within your application. Otherwise, I would say, why do you even bother using VueX except for notifying other part of your application that you are loading ;). That being said, here's my answer.
Using the ...mapState you have what you would be searching for, the computed reactivity of the state. This would happen especially during the invoke of the action. The action would then be changing, or what we call commit in VueX, the state (See doc: https://vuex.vuejs.org/guide/state.html)
Let's take your code and change it into a module with a namespace and then use the module in your vue (This is what I would do if the application is big, otherwise the same can be achieved using the mutation or no VueX at all):
const LOADING_STATE = 'LOADING_STATE'
export default {
namespaced: true, // Read the doc about that
state: {
loaded: false
},
mutations: {
[LOADING_STATE]: function (state, isLoading) {
state.loading = isLoading
}
},
actions: {
setLoading ({ commit }, isLoading) {
commit(LOADING_STATE, isLoading)
}
}
}
For your vue file where you have your template and your actions. It would look like this:
<script>
import { mapAction, mapState } from 'vuex'
exports default {
computed: {
...mapState({
// Here you could even have the full computation for the CSS class.
loading: state => state.loadingModule.loading,
// Like this... or you could use the getters that VueX does (search in the documentation since it's out of the scope of your question)
loadingCss: state => { return state.loadingModule.loading ? 'is-loading' : '' }
})
},
methods: {
// Usage of a namespace to avoid other modules in your VueX to have the same action.
...mapActions(['loadingModule/setLoading']),
}
}
</script>
And regarding your html template, you will be able to call the method this['loadingModule/setLoading'](true) or false and then the property that you can react to will be "loading".
While using promises, during your post or get or any other HTTP rest call, don't forget the context. If you're using Axios, after registering it in your VueJs context, I would do
this.$http.get('/my/api')
.then(response => {
// ... some code and also set state to ok ...
})
.catch(e => {
// set state to not loading anymore and open an alert
})
Let's complete your code now considering you're doing your HTTP(S) call somewhere.
<form #submit.prevent="postThis">
<div class="field">
<div class="control">
<!-- Here I would then use a computed property for that class (error). I would even put the a template or a v-if on a div in order to show or not all those html elements. That's you're choice and I doubt this is your final code ;) -->
<input class="input" :class="{ 'is-danger': errors.title }" type="text" id="title" placeholder="I have this idea to..." autofocus="" v-model="newTitle">
</div>
<p class="is-size-6 help is-danger" v-if="errors.title">
{{ errors.title[0] }}
</p>
</div>
<div class="field">
<div class="control">
<button #click="['loadingModule/setLoading'](true)" type="submit" :class="{'is-loading' : loading }">
Post
</button>
</div>
</div>
</form>
First, there is no need to have locally only needed state (loading) in global state (Vuex). So, typical usage looks like this:
<template>
<form>
<div class="field">
<div class="control">
<input
class="input" :class="{ 'is-danger': errors.title }"
type="text"
id="title"
placeholder="I have this idea to..."
autofocus=""
v-model="newTitle"
>
</div>
<p class="is-size-6 help is-danger" v-if="errors.title">
{{ errors.title[0] }}
</p>
</div>
<div class="field">
<div class="control">
<button
#click="postForm"
:class="{'is-loading': isLoading }"
>
Post
</button>
</div>
</div>
</form>
</template>
<script>
export default {
...
data () {
return {
...
newTitle: '',
isLoading: false,
response: null
}
},
methods: {
async postForm () {
try {
this.isLoading = true // first, change state to true
const { data } = await axios.post('someurl', { title: this.newTitle }) // then wait for some async call
this.response = data // save the response
} catch(err) {
// if error, process it here
} finally {
this.isLoading = false // but always change state back to false
}
}
}
}
</script>
if you using vuex like this. I guess you misunderstood vuex. Because you can use for local variable and you can check api result. if you want seperate api request, you have to mapAction in methods and mapGetters in Computed

VueJS - Element UI Input Component - Event handler "input" error

I'm trying to create a custom component with VueJS & Element-UI and I'm getting a very annoying error when trying to enter data into the input field.
Below are the files & the contents related to the issue:
components.js file:
Vue.component('yetti-input', {
props: ['value'],
template: '<el-input ref="input" v-bind:value="value" v-on:input="parseValue($event.target.value)"></el-input>',
methods: {
parseValue (value) {
this.$emit('input', value)
}
}
})
index.vue file:
<template>
<div>
<div class="login-form">
<yetti-form>
<yetti-input v-model="login.email"></yetti-input>
</yetti-form>
</div>
</div>
</template>
<script>
export default {
data () {
return {
login: {
email: '',
password: ''
}
}
}
}
</script>
Error I'm receiving in the Console:
Please point out if I'm being a fool, however I cannot for the life of me figure out what is going on.
Cheers,
Tim
Okay, I solved my problem.
Interestingly, the $event is the input value being provided when using el-input.
Rather than have: v-on:input="parseValue($event.target.value)"
I removed target.value and I had my value.
v-on:input="parseValue($event)"
Not sure if I've done the wrong thing by VueJS here. However, this has resolved my issue.

Categories

Resources