Search code examples
vue.jsvuejs2vuejs-transition

How to split CodePen into webpacked Components?


I am following this CodePen, and trying to fit it into the default VueJS 2.0 Boilerplate. This is how I would split the files up:

App.vue
main.js
components/Transitions.vue
components/Controls.vue
components/Page.vue

I am having huge problems getting this to run. E.g. my App.vue does not find the state. This is how I defined it in main.js:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

const state = {
    animations: ['fade', 'slide', 'slideUp', 'zoom', 'flipX', 'flipY'],
    view: 'slide'
}

new Vue({
  render: h => h(App),
  data() {
    return this.state
  }
}).$mount('#app')

This is my App.vue:

<template>
  <div id="app">
    <component :is="state.view">
      <h1>{{ state.view }}</h1>
    </component>
    <controls></controls>
  </div>
</template>

This would be my controls:

<template id="controls">
<ul class="controls">
  <li v-for="(animation, index) in state.animations" v-bind:key="index" @click.prevent="setView(animation)" v-bind:class="{ 'active': animation === state.view }">
    {{ animation }}
  </li>
</ul>
</template>

<script>
export default {
  template: '#controls',

  methods: {
    setView(animation) {
      this.state.view = animation
    }
  }
}
</script>

..and so forth. Unfortunately I am getting:

[Vue warn]: data functions should return an object:
https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function

(found in <Root>) vue.runtime.esm.js:619
[Vue warn]: Property or method "state" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

found in

---> <App> at src/App.vue
       <Root> vue.runtime.esm.js:619
[Vue warn]: Error in render: "TypeError: this.state is undefined"

What am I doing wrong here? How to get this thing to run?


Solution

  • Attaching data properties to the root instance does not make them globally available in all descendant components (which appears to be what you attempted). There are actually a few ways to share state across components, including Vuex or global event bus (e.g., using $root.emit() and $root.on()). However, another simple solution is to use Vue.observable along with a mixin, exported from a shared file:

    stateMixin.js:

    import { observable } from "vue";
    
    const state = observable({
      animations: ["fade", "slide", "slideUp", "zoom", "flipX", "flipY"],
      view: "slide"
    });
    
    // a mixin that declares a data propety named `state`
    export default {
      data() {
        return {
          state
        };
      }
    };
    

    Then, you could import that file into any component that needs to access state:

    App.vue:

    import stateMixin from '@/stateMixin'
    
    export default {
      mixins: [stateMixin],
      mounted() {
        console.log(this.state.view) // <-- stateMixin provides access to `state`
      }
    }
    

    demo of that Codepen in Codesandbox


    Also note that since you're converting to single-file-components (SFC), you should not export a template property. The SFC itself declares the template already, and the compiler knows to use that by default.