Open martijnvanloon opened 4 years ago
Automention: Hey @backbone87 @pksunkara @pocka, you've been tagged! Can you give a hand here?
I don't know much about vue so I can't answer your question fully. However, I can answer partially and hopefully one of our community vue experts can take it the rest of the way.
Here's a typical template:
The important part is props: Object.keys(argTypes)
. This is a list like, in your case, ['userMenuOpen', 'text']
. When you add this in a story function, @storybook/vue
automatically and transparently pulls them out of the args object and makes them available as dynamically updating props in your story.
How to further transform those props in the data()
function, etc., is outside of my vue knowledge, but I assume that it's something typical/standard. Hope that helps!
@martijnvanloon just so I fully understand, what's your objective in using data rather than props in your story components?
I think I understand where @martijnvanloon 's confusion is coming from. The args are being automatically passed as props to the component you are exporting as a story. So you don't have to add them to the component's data()
block, since they are passed in as props and accessible from the component already. If you don't either manually setup that component to expect those args as props, or use the automatic props: Object.keys(argTypes)
method, then I assume they will be living under this.$attrs
where all bound variables that aren't explicitly defined as props live.
Thank you for your help so far! Wallslide is onto something, I like and need to build components that have local state "data()". For example a "status" boolean that changes based on the results of an api call made by the component itself and that will display a "success" text (without sending events and props between "container" and "presentational" components).
The args passed with storybook indeed end up in the this.$attrs variable. To use these in the component I need to add code to my components reassigning the this.$attrs values on to my actual values.
I have written a working example below in the mounted hook:
When storybook is running it checks the "this.$attrs" variable It checks if keys in this object exist in the local Vue state and if they do it sets them it repeats this proces continually
Component:
data: function () {
return {
textExample: 'empty';
}
},
mounted() {
if (window.__STORYBOOK_ADDONS) {
setInterval(() => {
Object.entries(this.$attrs).forEach(([key, value]) => {
if (this[key] && this[key] !== value) {
this[key] = value;
}
});
}, 500);
}
}
Story example
export default {
title: 'nav/TopNavBar',
component: TopNavBar,
argTypes: {
textExample: { control: 'text' },
},
args: {
textExample: 'succesfully changed',
},
};
const Template = (args: any, { argTypes }: any) => ({
components: { TopNavBar },
template: '<top-nav-bar :textExample="textExample" />',
props: Object.keys(argTypes),
});
The above example gives me full "live" control over the data properties in my component with storybook controls. However adding code to my components that is only used for testing in storybook (and will bloat my application) seems like a bad practice. I could add this code globally to each component and make sure it does not compile to live, that would work pretty well.
Adding this mixin to storybooks preview.js checks if there are unspecified attributes supplied by a story and adds them to the components data property (if they exist). It requires no extra code in the components.
I came to realize it is often better to use props, but this is a good alternative for when props are not a fiting solution.
// bind non prop attributes to their corresponding data property
Vue.mixin({
mounted: function() {
if (Object.entries(this.$attrs).length > 0) {
this.intervalSb = setInterval(() => {
Object.entries(this.$attrs).forEach(([key, value]) => {
if (this[key] && this[key] !== value) {
this[key] = value;
}
});
}, 500);
}
},
destroyed() {
clearInterval(this.intervalSb);
}
});
If you want to keep rectivity, you might what to replace
this[key] = value;
with
this[key] = Vue.observable(value);
If you want to keep rectivity, you might what to replace
this[key] = value;
with
this[key] = Vue.observable(value);
Thanks! However when I do this and remove the interval from my code, reactivity (adjusting a control and seeing results) only works when in storybooks "Docs" tab but not the "Canvas" tab.
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!
change values in vue's data() property without my component using props.
Hi!. Probably you confused by 2 things:
So the simplest way is:
That was an "A-HA" moment for me as well and I spend too much time before realizing that.
Hey, I was half wrong, half right. While you can use the approach described above to simply pass data to the target component, you need to do some tricks to make it work with controls addon. Pay attention, you have to receive args
in story function as an argument, even if you don't need it. I use explicit arg names in example to make it clear, where data comes from.
export const StoryName = (args) => ({
components: { TheUser },
template: `<TheUser :name="nameFromArgs"></TheUser>`,
props: ["nameFromArgs"]
});
Default.args = {
nameFromArgs: "Leonardo!"
};
If you want to pass more arguments, you can use Object.keys()
to fill the props.
export const StoryName = args => ({
components: { TheUser },
template: `<TheUser :name="nameFromArgs" :age="ageFromArgs" :gender="genderFromArgs"></TheUser>`,
props: Object.keys(args)
});
StoryName.args = {
nameFromArgs: "Leonardo!",
ageFromArgs: 35,
genderFromArgs: "male"
};
You can go further and here is the option to auto-bind all the passed args to component properties. Pay attention, that you have to use exact prop name in args this time.
export const StoryName = args => ({
components: { TheUser },
template: `<TheUser v-bind="allPropsFromArgs"></TheUser>`,
props: {
allPropsFromArgs: {
default: () => args
}
}
});
StoryName.args = {
name: "Leonardo!",
age: 35,
gender: "male"
};
Still quite frustrated at the inability to change the internal state of the Vue component in Storybook. I don't want to introduce a prop, just for the sake of StoryBook - that's a smell to me, as it's a prop without use, except for StoryBook. I would really like there to be an ability to control the internal state, just like you do with props
Is there any development on this issue?
Back here to bump this issue up - not having access to the internal data of the component continues to be a major issue with using Storybook...
I've spend more than half day looking for the solution which is not possible at the moment, it is sad that Storybook has proper integration only with React and Vue support is lagging.
have same situation
Just a suggestion, but you could consider using play functions: https://storybook.js.org/docs/react/writing-stories/play-function#page-top
Play functions are indeed a great solution to a lot of scenarios, but there are still some cases when we need to force some value on a data property or override some method, and this is not currently feasible.
still relevant
still looking...
@larsrickert @chakAs3 Any ideas how we could implement this issue?
@larsrickert @chakAs3 Any ideas how we could implement this issue?
To be honest I don't really get the issue here. In my opinion the described behavior is how it should behave.
.vue
components). Only if you explicitly expose them with defineExpose()
you are able to access them. So this limitation seems to be done by design from the Vue team so I don't think we should find a workaround for this.My suggestion would be to use an atomic component architecture like this:
HomeView.vue
and Table.vue
component. Fetch your data in the HomeView
and pass it to the Table
as property. Like this, you can freely create Stories for your table and the data fetching is separated from the visual displaying of the dataPlease correct me if I got the point wrong 😄
There is a general design pattern for this called Atomic design
I fully agree with Lars. Manipulating internal data is generally discouraged in state management, especially within Vue. Even accessing component data should be explicitly exposed. Perhaps we should explore safer methods like unit testing or functional component with initial state.
I'm still open to find a proper implementation to achieve the final goal, i need to see an example for internal state manipulation use case.
I was under the impression that with storybook 6 I can change values in vue's data() property without my component using props. I Use TS and have declared two data properties: userMenuOpen and text. The code below reflects what i want to do. If it's not possible like with 5 I misunderstood.
Vue, typescript, storybook 6
Component:
Story: