I have a component story that requires an API call performed by an ACTION from my Vuex store. However, the store can't be found by Storybook: Unhandled promise rejection TypeError: "this.$store is undefined"
.
I've tried to access the store through the created
and mounted
Vue lifecycle hooks but each of them returned undefined
.
My Vuex store is correctly working inside my app.
I run on storybook 5.0.1
and vuex 3.1.1
.
Here's my storybook config.js
:
// Taken from https://davidwalsh.name/storybook-nuxt & https://github.com/derekshull/nuxt-starter-kit-v2/blob/master/.storybook/config.js
import { addParameters, configure } from '@storybook/vue';
import { withOptions } from '@storybook/addon-options';
import { setConsoleOptions } from '@storybook/addon-console';
import { create } from '@storybook/theming';
import Vue from 'vue';
import VueI18n from 'vue-i18n';
// Vue plugins
Vue.use(VueI18n);
setConsoleOptions({
panelExclude: [],
});
// Option defaults:
addParameters({
options: {
/**
* show story component as full screen
* @type {Boolean}
*/
isFullScreen: false,
/**
* display panel that shows a list of stories
* @type {Boolean}
*/
showNav: true,
/**
* display panel that shows addon configurations
* @type {Boolean}
*/
showPanel: true,
/**
* where to show the addon panel
* @type {String}
*/
panelPosition: 'bottom',
/**
* sorts stories
* @type {Boolean}
*/
sortStoriesByKind: false,
/**
* regex for finding the hierarchy separator
* @example:
* null - turn off hierarchy
* /\// - split by `/`
* /\./ - split by `.`
* /\/|\./ - split by `/` or `.`
* @type {Regex}
*/
hierarchySeparator: /\/|\./,
/**
* regex for finding the hierarchy root separator
* @example:
* null - turn off multiple hierarchy roots
* /\|/ - split by `|`
* @type {Regex}
*/
hierarchyRootSeparator: /\|/,
/**
* sidebar tree animations
* @type {Boolean}
*/
sidebarAnimations: true,
/**
* enable/disable shortcuts
* @type {Boolean}
*/
enableShortcuts: true,
/**
* theme storybook, see link below
*/
theme: create({
base: 'light',
brandTitle: '',
brandUrl: '',
// To control appearance:
// brandImage: 'http://url.of/some.svg',
}),
},
});
const req = require.context('../src/components', true, /\.story\.js$/)
function loadStories() {
req.keys().forEach((filename) => req(filename))
}
configure(loadStories, module);
Here's my component's story:
import { storiesOf } from '@storybook/vue';
import { withReadme } from 'storybook-readme';
import { withKnobs } from '@storybook/addon-knobs';
import HandoffMainView from './HandoffMainView.vue';
import readme from './README.md';
storiesOf('HandoffMainView', module)
.addDecorator(withReadme([readme]))
.addDecorator(withKnobs)
.add('Default', () => {
/* eslint-disable */
return {
components: { HandoffMainView },
data() {
return {
isLoading: true,
component: {
src: '',
data: [],
},
};
},
template: '<handoff-main-view :component="component" />',
};
});
Here's my component:
<template>
<main class="o-handoff-main-view">
<div class="o-handoff-main-view__content">
<div
:class="[
'o-handoff-main-view__background',
background ? `o-handoff-main-view__background--${background}` : false
]"
>
<loader
v-if="isLoading"
:color='`black`'
class="o-handoff-main-view__loader"
/>
<div
v-else
class="o-handoff-main-view__ui-component"
:style="getUiComponentStyle"
>
<img
:src="uiComponent.src"
alt=""
>
<handoff-main-view-layer-list
:layers="uiComponent.data"
/>
</div>
</div>
</div>
<div class="o-handoff-main-view__controls">
<handoff-main-view-zoom-handler
:default-zoom-level="zoomLevel"
:on-change="updateZoomLevel"
/>
</div>
</main>
</template>
<script>
import { mapActions } from 'vuex';
import Loader from '../../01-atoms/Loader/Loader.vue';
import HandoffMainViewZoomHandler from '../HandoffMainViewZoomHandler/HandoffMainViewZoomHandler.vue';
import HandoffMainViewLayerList from '../HandoffMainViewLayerList/HandoffMainViewLayerList.vue';
export default {
components: {
Loader,
HandoffMainViewZoomHandler,
HandoffMainViewLayerList,
},
props: {
background: {
type: String,
default: 'damier',
},
component: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: true,
zoomLevel: 1,
uiComponent: {
src: null,
}
};
},
mounted() {
this.setUiComponentImage();
},
methods: {
...mapActions('UiComponent', [
'ACTION_LOAD_SIGNED_URLS'
]),
async setUiComponentImage() {
const uiComponentImg = new Image();
const signedUrls = await this.ACTION_LOAD_SIGNED_URLS([this.component.id]);
uiComponentImg.onload = () => {
this.isLoading = false;
};
uiComponentImg.src = this.uiComponent.src;
},
},
};
</script>
You need to import the configure method from @storybook/vue to run Storybook and implement it to load stories (you'll learn what stories are soon): // . storybook/config. js import { configure } from '@storybook/vue'; import Vue from 'vue'; // Import your custom components.
You can't use Vuex without Vue. Because: Vuex checks for the existence of Vue. Vuex depends largely on Vue for its reactivity inner workings.
Vuex is a library that stores data in a Vuex store, which acts as the source of data on states in a Vue application. This store contains a global state (set of properties) and functions (getters, actions and mutations) used to read and alter the state.
This Vuex plugin allows you to sync and share the status of your Vue application across multiple tabs or windows using the local storage.
If you are using Nuxt.js, here is how you can do it:
./storybook/store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: require("../store/index.js").state,
getters: require("../store/index.js").getters,
actions: require("../store/index.js").actions,
mutations: require("../store/index.js").mutations,
modules: {
ads: {
namespaced: true,
state: require("../store/ads.js").state,
getters: require("../store/ads.js").getters,
actions: require("../store/ads.js").actions,
mutations: require("../store/ads.js").mutations
},
features: {
namespaced: true,
state: require("../store/features.js").state,
getters: require("../store/features.js").getters,
actions: require("../store/features.js").actions,
mutations: require("../store/features.js").mutations
},
user: {
namespaced: true,
state: require("../store/user.js").state,
getters: require("../store/user.js").getters,
actions: require("../store/user.js").actions,
mutations: require("../store/user.js").mutations
},
}
});
export default store
Then in your story:
// ...
import store from '@/.storybook/store';
export default {
title: 'MyComponent'
};
export const MyComponentStory = () => ({
store: store,
// ...
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With