euvl / vue-js-modal

Easy to use, highly customizable Vue.js modal library.
http://vue-js-modal.yev.io
MIT License
4.36k stars 594 forks source link

$ref undefined after closing #99

Closed atmar closed 7 years ago

atmar commented 7 years ago

Hello,

After the 'closed' event is fired, the $ref changes to 'undefined', giving me the following error :

client.js:50 TypeError: Cannot read property 'show' of undefined
    at VueComponent.showAuthModal (http://localhost:3000/_nuxt/layouts/default_nobg.af149a8269c959d47fb4.js:881:28)
    at boundFn (http://localhost:3000/_nuxt/common.17362cb3a494aa9cf32f.js:5562:14)
    at VueComponent.invoker (http://localhost:3000/_nuxt/common.17362cb3a494aa9cf32f.js:7200:18)
    at VueComponent.Vue.$emit (http://localhost:3000/_nuxt/common.17362cb3a494aa9cf32f.js:7710:18)
    at VueComponent.Vue.(anonymous function) [as $emit] (chrome-extension://nhdogjmejiglipccpnnnanhbledajbpd/build/backend.js:359:28)
    at VueComponent.showAuthModal (http://localhost:3000/_nuxt/layouts/default_nobg.af149a8269c959d47fb4.js:1279:12)
    at Proxy.boundFn (http://localhost:3000/_nuxt/common.17362cb3a494aa9cf32f.js:5562:14)
    at click (http://localhost:3000/_nuxt/layouts/default_nobg.af149a8269c959d47fb4.js:2407:23)
    at HTMLSpanElement.invoker (http://localhost:3000/_nuxt/common.17362cb3a494aa9cf32f.js:7200:18)

I didn't have this while developing this feature, it's only now, a few weeks later that I've noticed. I tried previous versions of the files that I know worked in the past, but no success. The only difference is that I did an 'npm update' to update everything. I've tried previous versions of this plugin but the result was the same. Currently trying different vuejs versions. I've been searching for hours but I don't know what changed that broke it. (Also I'm using Nuxt for ssr)

HeaderBar.vue:

<template>
  <header>
    </div-->
    <div class="pusher">
      <div class="ui container">
        <div class="header-main">
          <div class="ui grid">
            <div class="three wide computer fourteen wide mobile column">
              <nuxt-link class="menu-brand" to="/"> <img src="/images/logo.png" alt=""></nuxt-link>
            </div>
            <div class="seven wide computer fourteen wide mobile column">
              <search ref="search">
                <template slot="item" scope="option">
                  <div class="ui small image">
                    <img :src="`${$store.state.imageUrl}/assets/img/products/header/260x121/${option.image}`">
                  </div>
                  <div class="content">
                    <div class="header">{{ option.name }}</div>
                    <div class="meta">
                      <img :src="option.game_platform" class="image-icon">
                    </div>
                  </div>
                </template>
              </search>
            </div>
            <div class="six wide computer fourteen wide mobile column">
              <div class="right-side-wrapper">
                <div v-if="!isAuthenticated" class="wrap user">
                  <span class="login" @click="showAuthModal()" key="login">
                    Log In
                  </span>
                  <span class="register" @click="showAuthModal()" key="register">
                    Register
                  </span>
                </div>
                <div v-else class="wrap user">
                  <nuxt-link to="/account/orders">
                    <span>My Account</span>
                  </nuxt-link>
                </div>
                <cart></cart>
              </div>
            </div>
          </div>
        </div>

        <div class="menu-main">
          <nuxt-link class="menu-item" to="/">Home</nuxt-link>
          <nuxt-link class="menu-item" to="/games">Store</nuxt-link>
          <div class="menu-item">
            Best Selling
          </div>
          <div class="menu-item">
            Featured Games
          </div>
          <div class="menu-item">
            Support
          </div>
          <recent-viewed></recent-viewed>
        </div>
      </div>
    </div>
    <auth-modal ref="authModal"></auth-modal>
  </header>
</template>

<script>

import Search from '~/components/layout/header/Search.vue'
import RecentViewed from '~/components/layout/header/RecentViewed.vue'
import Cart from '~/components/layout/header/Cart.vue'

import AuthModal from '~/components/modals/AuthModal.vue'

export default {
  components: {
    Search, RecentViewed, Cart, AuthModal
  },

  computed: {
    isAuthenticated() {
      return this.$store.getters['auth/isAuthenticated']
    }
  },

  methods: {
    showAuthModal() {
      this.$refs.authModal.show()
    }
  }
}
</script>

Authmodal.vue

<template>
  <modal name="auth" :adaptive="true" :minHeight="515" :width="360" @before-close="beforeClose" @opened="opened">
    <div class="login-page">
      <div class="types">
        <div class="auth-type" :class="{ 'active': type == 'login'}" @click="type = 'login'">
          Sign In
        </div>
        <div class="auth-type" :class="{ 'active': type == 'register' }" @click="type = 'register'">
          Register
        </div>
      </div>
      <div class="form">

        <form v-if="type == 'login'" class="login-form" :key="'login'" @submit.prevent="login">
          <input v-model="email" type="email" name="email" placeholder="Email Address" required />
          <input v-model="password" type="password" name="password" placeholder="Password" required />

          <div v-if="invalidCredentials" class="invalid-cred">
            <span>Invalid username or password, try again</span>
          </div>
          <button type="submit" :disabled="pendingRequest">Sign In
            <div v-show="pendingRequest" style="margin-left:5px" class="ui active mini inverted inline loader"></div>
          </button>
          <p class="message">Not registered?
            <span @click="type = 'register'">Create an account</span>
          </p>
        </form>

        <form v-else class="register-form" :key="'register'" @submit.prevent="register">
          <input v-model="email" type="email" placeholder="Email Address" required/>
          <div class="error-label" v-if="errors.email">{{ generateErrors(errors.email) }}</div>

          <input v-model="password" type="password" placeholder="Password" required/>
          <span class="small-text">Password must be atleast 5 characters</span>
          <div class="error-label" v-if="errors.password">{{ generateErrors(errors.password) }}</div>

          <input v-model="confirmPassword" type="password" placeholder="Confirm Password" required/>
          <div class="error-label" v-if="!samePasswords">Passwords do not match</div>

          <button type="submit" :disabled="pendingRequest">Create
            <div v-show="pendingRequest" style="margin-left:5px" class="ui active mini inverted inline loader"></div>
          </button>
          <p class="message">Already registered?
            <span @click="type = 'login'">Sign In</span>
          </p>
        </form>

        <div class="ui horizontal divider">
          Or
        </div>

        <div class="social-buttons">

          <div class="soc-button">
            <div class="media-icon steam">
              <i class="steam icon"></i>
            </div>
            <div class="button steam">
              Sign In with Steam
            </div>
          </div>

          <div class="soc-button">
            <div class="media-icon fb">
              <i class="facebook f icon"></i>
            </div>
            <div class="button fb">
              Sign In with Facebook
            </div>
          </div>

          <div class="soc-button">
            <div class="media-icon google">
              <i class="google icon"></i>
            </div>
            <div class="button google">
              Sign In with Google
            </div>
          </div>

        </div>
      </div>
    </div>
  </modal>
</template>

<script>

import { generateErrors } from '~/utils/tools' // eslint-disable-line no-unused-vars

export default {

  data() {
    return {
      type: 'login',
      email: null,
      password: null,
      confirmPassword: null,
      samePasswords: true,
      invalidCredentials: false,
      pendingRequest: false,
      errors: []
    }
  },
  computed: {
  },

  methods: {
    login() {
      this.invalidCredentials = false
      this.pendingRequest = true

      this.$store.dispatch('auth/login', {
        email: this.email,
        password: this.password
      }).then((data) => {
        this.hide()

        this.invalidCredentials = false
        this.pendingRequest = false
      }).catch(() => {
        this.invalidCredentials = true
        this.pendingRequest = false
      })
    },
    register() {
      this.errors = []
      if (!this.checkPasswords()) {
        return
      }
      this.pendingRequest = true

      this.$store.dispatch('auth/register', {
        email: this.email,
        password: this.password,
        confirmPassword: this.confirmPassword
      }).then((data) => {
        this.hide()

        this.pendingRequest = false
      }).catch(({ response }) => {
        this.errors = response.data
        this.pendingRequest = false
      })
    },
    checkPasswords() {
      if (this.password === null || this.confirmPassword === null) {
        this.samePasswords = true
        return this.samePasswords
      }

      this.samePasswords = (this.password === this.confirmPassword)
      return this.samePasswords
    },
    show(type) {
      this.type = type
      this.$modal.show('auth')
    },
    hide() {
      this.$modal.hide('auth')
    },
    opened() {
      this._adaptive()
    },
    beforeClose() {
      // Enable scrolling of main page
      document.documentElement.style.overflow = null
    },
    _adaptive() {
      // Steps to have adaptive height for the modal
      setTimeout(() => {
        const modalDiv = document.querySelectorAll('div.v--modal-box')[0]
        modalDiv.style.height = 'auto'

        // Add scroll functionality and hide background
        const modalOverlay = document.querySelector('div.v--modal-overlay')
        modalOverlay.style.overflow = 'auto'
        modalOverlay.style.background = 'rgba(2,6,14,.8)'

        // Disable scrolling of main page, only allow for modal
        //  document.documentElement.style.overflow = 'hidden'
      }, 50)
    }
  }
}
</script>
euvl commented 7 years ago

Hey @atmar. After closed event is fired - modal is destroyed and i assume $refs should be cleaned.

From vue docs:

$refs is also non-reactive, therefore you should not attempt to use it in 
templates for data-binding.
atmar commented 7 years ago

Hi, thanks for the fast response. I still don't know why it suddenly doesn't work anymore, but I found somewhat of a fix to it. It suddenly came to me right after writing this post and realising it's my component that gets destroyed aswell and not only the modal. I've wrapped Authmodal.vue like this now:

<template>
<div>
<modal></modal>
<div>
</template>

By adding the div, the component survives the modal being destroyed.