A decorator is going to wrap every component. Since you’ve defined the application startup in a decorator, it gets called multiple times and you get the error you’re seeing.
I’m using F7/Vue/Storybook and I ended up with a config like this:
// Docs on configuration:
// https://github.com/storybookjs/storybook/blob/next/docs/src/pages/guides/guide-vue/index.md
// https://github.com/storybookjs/storybook/blob/17dcfb84194c9876d4452aae123443f9748f62b6/examples/vue-kitchen-sink/.storybook/config.js
//
// =========================================
// Added configurations for our dependencies
// =========================================
// Import Framework7
import Framework7 from 'framework7/framework7-lite.esm.bundle.js'
// Import Framework7-Vue Plugin
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js'
// Import Framework7 Styles
import 'framework7/css/framework7.bundle.css'
// Import Icons and App Custom Styles
import '../src/css/icons.css'
import '../src/css/app.scss'
// Init Framework7-Vue Plugin
// NOTE: this does not involve Vue directly at all, so some other configuration must be needed...
// unless F7.use() is calling Vue.use() as a side effect?
Framework7.use(Framework7Vue)
// =========================================
// Default configuration for storybook
// =========================================
import {configure, addParameters} from '@storybook/vue'
// addParameters is passing parameters to the app root which is a F7 component... but what registered the F7 components into Vue in the first place?
addParameters({
f7params: {
name: 'my-client', // App name
theme: 'auto', // Automatic theme detection
// App root data
data: function() {
return {}
},
// App routes
//routes: routes,
},
})
// automatically import all files ending in *.stories.js
configure(require.context('../src/stories', true, /\.stories\.js$/), module)
Note that on the F7 side I still have confusion around the loading process, as noted above. At what point does Vue.use actually get called to register the F7 components? Is that happening as a side effect of F7.use()? That seems pretty magical and nonintuitive if it’s the case. Can anyone from F7 comment?
Scratch that, I see what’s happening. You’re right to use the App decorator. The problem is that Framework7 calls F7.init() when the App component is mounted (same for React). So you’re in a pickle - you have to decorate your stories with this in order for them to be styled properly and have the right this properties on them, but it blows up when you render it multiple times.
What to do? I just imported/extended the App component and overrode the mounted method with my own that implements a singleton pattern.
import app from 'framework7-vue/components/app'
import f7 from 'framework7-vue/utils/f7'
let hasRun = false
export default Object.assign(app, {
mounted() {
const self = this
const {params = {}, routes} = self.props
const el = self.$refs.el
const parentEl = el.parentNode
if (parentEl && parentEl !== document.body && parentEl.parentNode === document.body) {
parentEl.style.height = '100%'
}
// added this code to protect with singleton and avoid re-initialization
if (!hasRun) {
hasRun = true
f7.init(el, params, routes)
}
},
})
Again, the same pattern should work for React.
F7 developers: Are there any contexts in which it would not be ok to do this? It would be nice to have builtin compatibility with an outside rendering or testing library.
Only with F7 update where mounted can be potentially modified. I guess, it is easier to add similar check to the f7 itself and don’t init it again in this case
Yes, that was my thought. If F7 had this check it would work without issue in environments like Storybook, Jest, Mocha, etc.
Edit: or even better if the init() method itself checked to see if it has already been run and spit out a warning and return instead of blowing up. Then it would not need to be reimplemented in every UI framework plugin.