Introduction
Over the past few weeks I’ve been updating We Make Websites’ internal framework with Storybook so we had a single place to see a projects’ components in a sandboxed view.
While doing this I found it particularly tricky to dynamically update the args passed to the Vue component from an event in the Vue component itself. The component in question was a quantity selector with buttons to increment and decrement a value displayed in its input.
When I researched it there didn’t seem to be a straightforward solution, the Storybook controls available to update the arg
worked but I thought this wasn’t the best way to simulate the different button states based on the quantity value.
By piecing together various GitHub issues and forum posts I built the below solution. It’s worth noting I’m not a Storybook expert so this could have some unforeseen consequence but it works in my testing with Vue 3.
Solution
The solution revolves around the updateArgs()
function available in the useArgs()
function which is only available as a decorator. The trouble with it being a decorator is that you can’t directly access it within a story so first we have to make it available.
As noted in the decorators documentation you can use the .storybook/preview.js file to globally set decorators. Make sure any component-level decorators don’t override it.
1import { useArgs } from '@storybook/client-api'
2
3export const decorators = [
4 (story, context) => {
5 const [_, updateArgs] = useArgs()
6 return story({ ...context, updateArgs })
7 },
8]
The above export makes the updateArgs()
function globally available. The export
is an array which allows you to add further customisations such as globally setting the template:
1export const decorators = [
2 (story, context) => {
3 const [_, updateArgs] = useArgs()
4 return story({ ...context, updateArgs })
5 },
6 () => ({ template: '<story />' }),
7]
Now in your story template you can access the updateArgs()
function as below:
1const Template = (args, { updateArgs }) => ({
2 components: { QuantitySelector },
3 setup() {
4 return { args }
5 },
6 template: '<quantity-selector v-bind="args" @update-quantity="handleQuantityUpdate" />',
7 methods: {
8 handleQuantityUpdate(quantity) {
9 updateArgs({ ...args, quantity })
10 },
11 },
12})
The <quantity-selector>
component already emits the update-quantity
custom event when the buttons are clicked (see custom events documentation). This custom event is used in the template
to trigger a custom method
which uses the updateArgs
function.
Passing the existing args
variable as a destructured object inside a new object with the new quantity
variable is a shorthand way to update only the quantity
variable’s value. If you passed just the quantity
variable then you would lose all your other args
.