diff --git a/docs/snippets/angular/loader-story.mdx.mdx b/docs/snippets/angular/loader-story.mdx.mdx new file mode 100644 index 000000000000..e1f84461b134 --- /dev/null +++ b/docs/snippets/angular/loader-story.mdx.mdx @@ -0,0 +1,28 @@ +```md + + +import { Meta, Story } from '@storybook/addon-docs/blocks'; + +import TodoItem from './TodoItem'; + +import fetch from 'node-fetch'; + + + + ({ + todo: await ( + await fetch("https://jsonplaceholder.typicode.com/todos/1") + ).json(), + }), + ]} +> + {(args, { loaded: { todo } }) => ({ + props: { + todo: todo, + }, + })} + +``` \ No newline at end of file diff --git a/docs/snippets/angular/loader-story.ts.mdx b/docs/snippets/angular/loader-story.ts.mdx new file mode 100644 index 000000000000..837cd804a9a5 --- /dev/null +++ b/docs/snippets/angular/loader-story.ts.mdx @@ -0,0 +1,37 @@ +```ts +// TodoItem.stories.ts + +import { moduleMetadata, Story, Meta } from '@storybook/angular'; + +import fetch from 'node-fetch'; + +import { CommonModule } from '@angular/common'; + +import TodoItem from './TodoItem'; + +export default { + component: TodoItem, + decorators: [ + moduleMetadata({ + declarations: [TodoItem], + imports: [CommonModule], + }), + ], + title: 'Examples/Loader', +} as Meta; + +export const Primary = (args, { loaded: { todo } }) => { + return { + props: { + args, + todo, + }, + }; +}; + +Primary.loaders = [ + async () => ({ + todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), + }), +]; +``` \ No newline at end of file diff --git a/docs/snippets/svelte/storybook-preview-global-loader.js.mdx b/docs/snippets/common/storybook-preview-global-loader.js.mdx similarity index 56% rename from docs/snippets/svelte/storybook-preview-global-loader.js.mdx rename to docs/snippets/common/storybook-preview-global-loader.js.mdx index 8c10b6a19050..2b7d8f057d48 100644 --- a/docs/snippets/svelte/storybook-preview-global-loader.js.mdx +++ b/docs/snippets/common/storybook-preview-global-loader.js.mdx @@ -5,7 +5,7 @@ import fetch from 'node-fetch'; export const loaders = [ async () => ({ - currentUser: (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(), + currentUser: await (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(), }), ]; ``` \ No newline at end of file diff --git a/docs/snippets/react/loader-story.js.mdx b/docs/snippets/react/loader-story.js.mdx index b5ea01dec7c3..d901a012173a 100644 --- a/docs/snippets/react/loader-story.js.mdx +++ b/docs/snippets/react/loader-story.js.mdx @@ -7,6 +7,11 @@ import fetch from 'node-fetch'; import { TodoItem } from './TodoItem'; +export default { + component: TodoItem, + title: 'Examples/Loader', +}; + export const Primary = (args, { loaded: { todo } }) => ; Primary.loaders = [ async () => ({ diff --git a/docs/snippets/react/loader-story.mdx.mdx b/docs/snippets/react/loader-story.mdx.mdx new file mode 100644 index 000000000000..71b0e979a36d --- /dev/null +++ b/docs/snippets/react/loader-story.mdx.mdx @@ -0,0 +1,26 @@ +```md + + +import { Meta, Story } from '@storybook/addon-docs'; + +import fetch from 'node-fetch'; + +import { TodoItem } from './TodoItem'; + + + + ({ + todo: await ( + await fetch("https://jsonplaceholder.typicode.com/todos/1") + ).json(), + }), + ]} +> + {(args, { loaded: { todo } }) => ( + + )} + +``` \ No newline at end of file diff --git a/docs/snippets/react/storybook-preview-global-loader.js.mdx b/docs/snippets/react/storybook-preview-global-loader.js.mdx deleted file mode 100644 index 1b005cbdf353..000000000000 --- a/docs/snippets/react/storybook-preview-global-loader.js.mdx +++ /dev/null @@ -1,13 +0,0 @@ -```js -// .storybook/preview.js - -import React from 'react'; - -import fetch from 'node-fetch'; - -export const loaders = [ - async () => ({ - currentUser: (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(), - }), -]; -``` \ No newline at end of file diff --git a/docs/snippets/svelte/loader-story.js.mdx b/docs/snippets/svelte/loader-story.js.mdx index cb17b610f1ae..7fc57ea8be6a 100644 --- a/docs/snippets/svelte/loader-story.js.mdx +++ b/docs/snippets/svelte/loader-story.js.mdx @@ -5,14 +5,19 @@ import fetch from 'node-fetch'; import TodoItem from './TodoItem.svelte'; +export default { + component: TodoItem, + title: 'Examples/Loader', +}; + export const Primary = (args, { loaded: { todo } }) => ({ Component: TodoItem, - props: {...args, ...todo}, + props: { ...args, ...todo }, }); Primary.loaders = [ async () => ({ - todo: (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), + todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), }), ]; ``` \ No newline at end of file diff --git a/docs/snippets/svelte/loader-story.mdx.mdx b/docs/snippets/svelte/loader-story.mdx.mdx new file mode 100644 index 000000000000..fcda10e867d9 --- /dev/null +++ b/docs/snippets/svelte/loader-story.mdx.mdx @@ -0,0 +1,30 @@ +```md + + +import { Meta, Story } from '@storybook/addon-docs'; + +import TodoItem from './TodoItem.svelte'; + +import fetch from 'node-fetch'; + + + + ({ + todo: await ( + await fetch("https://jsonplaceholder.typicode.com/todos/1") + ).json(), + }), + ]} +> + {(args, { loaded: { todo } }) => ({ + Component: SampleLoaderComponent, + props: { + ...args, + todo, + }, + })} + +``` \ No newline at end of file diff --git a/docs/snippets/vue/loader-story.3.js.mdx b/docs/snippets/vue/loader-story.3.js.mdx new file mode 100644 index 000000000000..fd8c09308512 --- /dev/null +++ b/docs/snippets/vue/loader-story.3.js.mdx @@ -0,0 +1,28 @@ +```js +// TodoItem.stories.js + +import TodoItem from './TodoItem.vue'; + +import fetch from 'node-fetch'; + +export default { + component: TodoItem, + title: 'Examples/Loader', +}; + +export const Primary = (args, { loaded: { todo } }) => { + return { + components: { TodoItem }, + setup() { + return { args, todo: todo }; + }, + template: ``, + }; +}; + +Primary.loaders = [ + async () => ({ + todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), + }), +]; +``` \ No newline at end of file diff --git a/docs/snippets/vue/loader-story.mdx.mdx b/docs/snippets/vue/loader-story.mdx.mdx new file mode 100644 index 000000000000..8d3e8e8cf9c8 --- /dev/null +++ b/docs/snippets/vue/loader-story.mdx.mdx @@ -0,0 +1,30 @@ +```md + + +import { Meta, Story } from '@storybook/addon-docs'; + +import TodoItem from './TodoItem.vue'; + +import fetch from 'node-fetch'; + + + + ({ + todo: await ( + await fetch("https://jsonplaceholder.typicode.com/todos/1") + ).json(), + }), + ]} +> + {(args, { loaded: { todo } }) => ({ + components: { TodoItem }, + setup() { + return { args, todo: todo }; + }, + template: ``, + })} + +``` \ No newline at end of file diff --git a/docs/writing-stories/loaders.md b/docs/writing-stories/loaders.md index af5a8bd4568e..c84de903cad5 100644 --- a/docs/writing-stories/loaders.md +++ b/docs/writing-stories/loaders.md @@ -2,30 +2,36 @@ title: 'Loaders (experimental)' --- -Loaders (experimental) are asynchronous functions that load data for a story and its [decorators](./decorators.md). A story's loaders run before the story renders, and the loaded data is passed into the story via its render context. +Loaders (experimental) are asynchronous functions that load data for a story and its [decorators](./decorators.md). A story's loaders run before the story renders, and the loaded data injected into the story via its render context. -Loaders can be used to load any asset, typically as a performance optimization. They were designed for to lazy load components and other large story imports. They can also be used to load remote API data to be used in a story. However, [Args](./args.md) is the recommended way to manage story data, and we're building up an ecosystem of tools and techniques around Args which might not be compatible with loaded data. +Loaders can be used to load any asset, lazy load components, or fetch data from a remote API. This feature was designed as a performance optimization to handle large story imports. However, [Args](./args.md) is the recommended way to manage story data. We're building up an ecosystem of tools and techniques around Args that might not be compatible with loaded data. -Loaders are an advanced feature ("escape hatch") and we only recommend using them if you have a specific need that can't be fulfilled by other means. They are experimental in Storybook 6.1 and the APIs are subject to change outside of the normal semver cycle. +They are an advanced feature (i.e., escape hatch), and we only recommend using them if you have a specific need that other means can't fulfill. They are experimental in Storybook 6.1, and the APIs are subject to change outside of the normal semver cycle. ## Fetching API data -Stories are isolated component examples that render internal data that's defined as part of the story or alongside the story as [args](./args.md). +Stories are isolated component examples that render internal data defined as part of the story or alongside the story as [args](./args.md). -Loaders are useful when you need to load story data externally, e.g. from a remote API. Consider the following example that fetches a todo item for display in a todo list: +Loaders are helpful when you need to load story data externally (e.g., from a remote API). Consider the following example that fetches a todo item to display in a todo list: -The loaded data is combined into a `loaded` field on the story context, which is the second argument to a story function. In this example we spread the story's args in first, so they take priority over the static data provided by the loader. +The response obtained from the remote API call is combined into a `loaded` field on the story context, which is the second argument to a story function. For example, in React, the story's args were spread first to prioritize them over the static data provided by the loader. With other frameworks (e.g., Angular), you can write your stories as you'd usually do. ## Global loaders @@ -35,20 +41,19 @@ We can also set a loader for **all stories** via the `loaders` export of your [` -In this example, we load a "current user" that is available as `loaded.currentUser` for all stories. +In this example, we load a "current user" available as `loaded.currentUser` for all stories. ## Loader inheritance -Like parameters, loaders can be defined globally, at the component level and for a single story (as we’ve seen). +Like [parameters](./parameters.md), loaders can be defined globally, at the component level, and for a single story (as we’ve seen). -All loaders, defined at all levels that apply to a story, run before the story is rendered. +All loaders, defined at all levels that apply to a story, run before the story renders in Storybook's canvas. - All loaders run in parallel - All results are the `loaded` field in the story context @@ -63,3 +68,5 @@ Loaders have the following known limitations: - They are not yet compatible with the storyshots addon ([#12703](https://github.com/storybookjs/storybook/issues/12703)). - They are not yet compatible with inline-rendered stories in Storybook Docs ([#12726](https://github.com/storybookjs/storybook/issues/12726)). + +If you're interested in contributing to this feature, read our [contribution guide](../contribute/how-to-contribute.md) and submit a pull request with your work. \ No newline at end of file