Hello!
I’m encountering a bug on iOS browsers (both Safari and Chrome) whose origin is very mysterious to me.
The setup is like so:
I have a component composed of two other components: a login form inside of a modal, conditionally displayed depending on a vuex property.
The project is using Webpack and single-file components, compiled using vue-loader.
There are 3 files involved in this bug:
-
LoginModal.vue
which is just a wrapper around -
Overlay.vue
, a container component being used to wrap -
LoginForm.vue
, a login form
The problem I’m encountering is that when I type into the text inputs, after I type a second character, the form disappears while the modal remains visible. The form will reappear on a touchmove event.
EDIT - ok, so after the refactor of placing the form code directly in the component, it seems the improvement is marginal or nonexistent and the improvements i was seeing initially were probably caused by other factors. It’s also become clear that this bug only appears on typing the second character. If I get the form to reappear by closing and reopening the modal, the typing proceeds normally and the form remains on the screen. If I get it to reappear by touchmove, the problem persists. Weeeird.
The problem first started occurring when I refactored the login form, separating it into its own file. I was already importing the overlay from its own .vue
file since it is used in many components across the site, but I was not importing the login form itself. It was written inline. It’s when the login form was being imported into a third component that the problem arose.
If I remove the v-if
in the overlay component or set the property it depends on to true
, the problem goes away. If I write the form directly into LoginModal.vue
, the problem goes away. The first fix is obviously not applicable. The second would work, but would duplicate code and I’d like to avoid that if I can.
The problem appears on iOS only and only using the file structure where the login form and the overlay are both imported into a third file that composes them. So, I recognize that this is a very…niche bug, but interesting nevertheless.
I suspect 2 things:
- The problem may be originating from vue-loader somehow, since it goes away if I reduce the 3 component files to 2.
- The problem is likely performance related.
Let me know if anybody has any insight. Sorry this post has become so long, and thanks in advance!
Here’s some code:
// LoginModal.vue
<template>
<overlay v-if="loginFormIsActive" content-class="login__content">
<login-form></login-form>
</overlay>
</template>
<script>
import { mapGetters } from 'vuex';
import overlay from '../container/Overlay.vue';
import loginForm from './LoginForm.vue';
export default {
computed: { ...mapGetters(['loginFormIsActive']),
components: {
'overlay': overlay,
'login-form' : loginForm
}
}
</script>
// Overlay.vue
<template>
<transition name="fade">
<div class="overlay">
<div class="overlay__wrapper">
<div class="overlay__header">
<a class="overlay__collapse" v-on:click.prevent="closeModal"></a>
</div>
<div class="overlay__content" :class="contentClass">
<slot></slot>
</div>
</div>
</div>
</transition>
</template>
<script>
import { mapMutations } from 'vuex';
export default {
props: ['contentClass'],
methods: {
...mapMutations(['closeModal'])
}
}
</script>
<style lang="scss">
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0
}
</style>
// LoginForm.vue
<template>
<form class="form">
<p class="form__message--error">{{loginMessage}}</p>
<input class="form__text-input" type="text" v-model="credentials.username" placeholder="username or email">
<input class="form__text-input" type="password" v-model="credentials.password" placeholder="password">
<p class="form__fine-print"><a :href="recoverLink">Forgot your password?</a></p>
<submit-button class="form__submit" button-text="Submit" :callback="submit" associated-action="userLogin"></submit-button>
</form>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex';
import submitButton from './SubmitButton.vue';
import MODALS from '../../constants/Modals.js';
import NAVIGATION from '../../constants/Navigation.js';
export default {
data(){
return {
credentials: {
username: '',
password: '',
},
privacyLink: `${NAVIGATION.SITE_PATH}/privacy`,
termsLink: `${NAVIGATION.SITE_PATH}/terms`
}
},
computed: {
...mapState({
userIsLoggedIn: state => state.user.userIsLoggedIn,
loginMessage: state => state.user.loginMessage,
activeModal: state => state.activeModal,
prompt: state => state.login.prompt
}),
hasError: function(){
return this.validationErrors.any();
},
recoverLink: () => NAVIGATION.WEBROOT + '/recover'
},
methods: {
...mapMutations(['openModal']),
...mapMutations('login', ['resetLoginPrompt']),
...mapActions('user', [
'userLogin',
'userLogout'
]),
submit(){
const credentials = {
_username: this.credentials.username,
_password: this.credentials.password
};
this.userLogin(credentials)
.then();
},
logoutUser: function(){
this.userLogout();
},
openRegisterForm(){
this.openModal(MODALS.REGISTER_FORM)
}
},
watch: {
userIsLoggedIn: function(theyAre){
if ( theyAre && this.activeModal === MODALS.LOGIN_FORM ) {
window.setTimeout(()=>{this.closeModal()}, 200);
}
}
},
components:{
'submit-button': submitButton
}
}
</script>