diff --git a/core/config/test/__snapshots__/stories.test.ts.snap b/core/config/test/__snapshots__/stories.test.ts.snap index 1173a9e25..c9df6997d 100644 --- a/core/config/test/__snapshots__/stories.test.ts.snap +++ b/core/config/test/__snapshots__/stories.test.ts.snap @@ -64,13 +64,13 @@ Array [ "/Users/atanasster/component-controls/ui/blocks/src/Subtitle/Subtitle.stories.tsx", "/Users/atanasster/component-controls/ui/blocks/src/TagsList/TagsList.stories.tsx", "/Users/atanasster/component-controls/ui/blocks/src/Title/Title.stories.tsx", - "/Users/atanasster/component-controls/examples/stories/src/stories_native/async-stories.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories_native/dynamic-stories.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors-starter.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors.stories.jsx", "/Users/atanasster/component-controls/examples/stories/src/stories/inherit-from-doc.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/smart-prop-type.stories.js", "/Users/atanasster/component-controls/examples/stories/src/stories/smart-typescript.stories.js", + "/Users/atanasster/component-controls/examples/stories/src/stories/stories-async.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/stories-esm.stories.js", ] `; @@ -139,13 +139,13 @@ Array [ "/Users/atanasster/component-controls/ui/blocks/src/Subtitle/Subtitle.stories.tsx", "/Users/atanasster/component-controls/ui/blocks/src/TagsList/TagsList.stories.tsx", "/Users/atanasster/component-controls/ui/blocks/src/Title/Title.stories.tsx", - "/Users/atanasster/component-controls/examples/stories/src/stories_native/async-stories.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories_native/dynamic-stories.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors-starter.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/controls-editors.stories.jsx", "/Users/atanasster/component-controls/examples/stories/src/stories/inherit-from-doc.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/smart-prop-type.stories.js", "/Users/atanasster/component-controls/examples/stories/src/stories/smart-typescript.stories.js", + "/Users/atanasster/component-controls/examples/stories/src/stories/stories-async.stories.tsx", "/Users/atanasster/component-controls/examples/stories/src/stories/stories-esm.stories.js", ] `; diff --git a/core/core/README.md b/core/core/README.md index a47bc4369..27df5d666 100644 --- a/core/core/README.md +++ b/core/core/README.md @@ -61,6 +61,7 @@ - [PackageDependency](#packagedependency) - [StoryRenderFn](#storyrenderfn) - [defaultExport](#defaultexport) + - [useAsync](#useasync) - [BuildConfiguration](#buildconfiguration) - [ControlsConfig](#controlsconfig) - [PageLayoutProps](#pagelayoutprops) @@ -142,7 +143,7 @@ $ npm install @component-controls/core --save-dev ## DefaultStore -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L361)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L356)_ @@ -206,7 +207,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr store of stories information in memory after the loader is applied -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L322)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L317)_ @@ -263,7 +264,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr list of components used in stories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L297)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L292)_ Record<string, @@ -277,7 +278,7 @@ A documentation file's metadata. For MDX files, fromtmatter is used to declare the document properties. For ESM (ES Modules) documentation files, the default export is used. -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L186)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L181)_ ### properties @@ -308,7 +309,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr list of story files, or groups -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L302)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L297)_ Record<string, @@ -320,7 +321,7 @@ Record<string, list of repositories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L314)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L309)_ Record<string, @@ -330,13 +331,13 @@ Record<string, ## Pages -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L304)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L299)_ [Document](#document)\[] ## StoreObserver -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L316)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L311)_ **function** (`story`: [Story](#story)): void; @@ -351,7 +352,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr list of stories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L309)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L304)_ Record<string, @@ -370,7 +371,6 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr | Name | Type | Description | | ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `arguments` | [StoryArguments](#storyarguments) | arguments passed to the story function. eg \`export const story = props => <Story {...props} />;\` | -| `async` | boolean | if the story is declared as an async function | | `description` | string | story extended description. can use markdown. | | `doc` | string | title of the file/group of stories | | `dynamic` | boolean | if set to true, the function is dynamically creating stories, returns a list of Story objects | @@ -397,7 +397,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr dynamic story creator function type. returns an array of dynamically loaded stories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L178)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L173)_ **function** (`doc`\*: [Document](#document)): ### properties| Name | Type | Description | | ----------- | ------------------ | ----------- | @@ -413,19 +413,19 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr ## CURRENT_STORY -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L318)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L313)_ ## defDocType -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L180)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L175)_ ## dateToLocalString -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L287)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L282)_ **function** dateToLocalString(`date`: [Date](#date)): string; @@ -438,7 +438,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr ## getDefaultStore -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L387)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L382)_ **function** getDefaultStore(): [Store](#store); @@ -1051,6 +1051,20 @@ _defined in [@component-controls/core/src/utility.ts](https://github.com/ccontro +## useAsync + +_defined in [@component-controls/core/src/utility.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/utility.ts#L183)_ + +**function** useAsync(`asyncFunction`\*: **function** (): Promise<>;, `immediate`\*: boolean): **error**: **execute**: [(Anonymous function)](<#(anonymous function)>)**status**: 'idle' | 'pending' | 'success' | 'error'**value**: ; + +### parameters + +| Name | Type | Description | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `asyncFunction*` | **function** (): Promise<>; | | +| `immediate*` | boolean | | +| `returns` | **error**: **execute**: [(Anonymous function)](<#(anonymous function)>)**status**: 'idle' \| 'pending' \| 'success' \| 'error'**value**: | | + ## BuildConfiguration global configuration used at build time @@ -1195,16 +1209,16 @@ render function by framework. By default 'react' _defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L11)_ -**function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): Promise<[ReactElement](#reactelement)>; +**function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): [ReactElement](#reactelement); ### parameters -| Name | Type | Description | -| --------- | ----------------------------------------- | ----------- | -| `story*` | [Story](#story) | | -| `doc` | [Document](#document) | | -| `options` | any | | -| `returns` | Promise<[ReactElement](#reactelement)> | | +| Name | Type | Description | +| --------- | ----------------------------- | ----------- | +| `story*` | [Story](#story) | | +| `doc` | [Document](#document) | | +| `options` | any | | +| `returns` | [ReactElement](#reactelement) | | ## PageConfiguration @@ -1355,7 +1369,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c ## StoreObserver -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L316)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L311)_ **function** (`story`: [Story](#story)): void; @@ -1377,7 +1391,6 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr | Name | Type | Description | | ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `arguments` | [StoryArguments](#storyarguments) | arguments passed to the story function. eg \`export const story = props => <Story {...props} />;\` | -| `async` | boolean | if the story is declared as an async function | | `description` | string | story extended description. can use markdown. | | `doc` | string | title of the file/group of stories | | `dynamic` | boolean | if set to true, the function is dynamically creating stories, returns a list of Story objects | @@ -1394,7 +1407,7 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr list of components used in stories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L297)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L292)_ Record<string, @@ -1417,7 +1430,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c list of story files, or groups -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L302)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L297)_ Record<string, @@ -1429,7 +1442,7 @@ Record<string, list of repositories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L314)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L309)_ Record<string, @@ -1441,7 +1454,7 @@ Record<string, list of stories -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L309)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L304)_ Record<string, @@ -1486,7 +1499,7 @@ A documentation file's metadata. For MDX files, fromtmatter is used to declare the document properties. For ESM (ES Modules) documentation files, the default export is used. -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L186)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L181)_ ### properties @@ -1590,16 +1603,16 @@ render function by framework. By default 'react' _defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L11)_ -**function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): Promise<[ReactElement](#reactelement)>; +**function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): [ReactElement](#reactelement); ### parameters -| Name | Type | Description | -| --------- | ----------------------------------------- | ----------- | -| `story*` | [Story](#story) | | -| `doc` | [Document](#document) | | -| `options` | any | | -| `returns` | Promise<[ReactElement](#reactelement)> | | +| Name | Type | Description | +| --------- | ----------------------------- | ----------- | +| `story*` | [Story](#story) | | +| `doc` | [Document](#document) | | +| `options` | any | | +| `returns` | [ReactElement](#reactelement) | | ## ActionItems @@ -1663,7 +1676,6 @@ _defined in [@component-controls/core/src/document.ts](https://github.com/ccontr | Name | Type | Description | | ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `arguments` | [StoryArguments](#storyarguments) | arguments passed to the story function. eg \`export const story = props => <Story {...props} />;\` | -| `async` | boolean | if the story is declared as an async function | | `description` | string | story extended description. can use markdown. | | `doc` | string | title of the file/group of stories | | `dynamic` | boolean | if set to true, the function is dynamically creating stories, returns a list of Story objects | @@ -1688,7 +1700,7 @@ A documentation file's metadata. For MDX files, fromtmatter is used to declare the document properties. For ESM (ES Modules) documentation files, the default export is used. -_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L186)_ +_defined in [@component-controls/core/src/document.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/document.ts#L181)_ ### properties diff --git a/core/core/src/configuration.ts b/core/core/src/configuration.ts index 4484e71c0..f44fe5926 100644 --- a/core/core/src/configuration.ts +++ b/core/core/src/configuration.ts @@ -12,7 +12,7 @@ export type FrameworkRenderFn = ( story: Story, doc?: Document, options?: any, -) => Promise; +) => ReactElement; /** * story type pages can have multiple tabs with separate page configurations. diff --git a/core/core/src/document.ts b/core/core/src/document.ts index 9e3e65fa1..40ad842ec 100644 --- a/core/core/src/document.ts +++ b/core/core/src/document.ts @@ -164,11 +164,6 @@ export type Story = { * it is set internally and will be used to create a story URL */ dynamicId?: string; - - /** - * if the story is declared as an async function - */ - async?: boolean; } & StoryProps; /** diff --git a/core/core/src/utility.ts b/core/core/src/utility.ts index a22b4dc11..565896049 100644 --- a/core/core/src/utility.ts +++ b/core/core/src/utility.ts @@ -1,4 +1,4 @@ -import React, { MouseEvent } from 'react'; +import React, { MouseEvent, useEffect, useState, useCallback } from 'react'; /** * position in the stories source code @@ -178,3 +178,46 @@ export interface ActionItem { } export type ActionItems = ActionItem[]; + +// source https://usehooks.com/useAsync/ +export const useAsync = ( + asyncFunction: () => Promise, + immediate = true, +) => { + const [status, setStatus] = useState< + 'idle' | 'pending' | 'success' | 'error' + >('idle'); + const [value, setValue] = useState(null); + const [error, setError] = useState(null); + + // The execute function wraps asyncFunction and + // handles setting state for pending, value, and error. + // useCallback ensures the below useEffect is not called + // on every render, but only if asyncFunction changes. + const execute = useCallback(() => { + setStatus('pending'); + setValue(null); + setError(null); + return asyncFunction() + .then((response: any) => { + setStatus('success'); + setValue(response); + }) + .catch((error: any) => { + setError(error); + setStatus('error'); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Call execute if we want to fire it right away. + // Otherwise execute can be called later, such as + // in an onClick handler. + useEffect(() => { + if (immediate) { + execute(); + } + }, [execute, immediate]); + + return { execute, status, value, error }; +}; diff --git a/core/instrument/src/babel/esm-stories.ts b/core/instrument/src/babel/esm-stories.ts index ae810541a..2117c5fb1 100644 --- a/core/instrument/src/babel/esm-stories.ts +++ b/core/instrument/src/babel/esm-stories.ts @@ -31,9 +31,6 @@ export const extractCSFStories = ( name, id: name, }; - if (declaration.init.async) { - story.async = true; - } traverse(path.node, extractFunctionParameters(story), path.scope); return story; } @@ -55,9 +52,6 @@ export const extractCSFStories = ( name, id: name, }; - if (declaration.async) { - story.async = true; - } traverse(path.node, extractFunctionParameters(story), path.scope); return story; } diff --git a/core/instrument/src/babel/mdx-stories.ts b/core/instrument/src/babel/mdx-stories.ts index 2233bd9dc..97c7f524a 100644 --- a/core/instrument/src/babel/mdx-stories.ts +++ b/core/instrument/src/babel/mdx-stories.ts @@ -127,15 +127,6 @@ export const extractMDXStories = (props: any) => ( name, id, }; - if ( - expression && - expression.expression && - expression.expression.type === 'ArrowFunctionExpression' - ) { - if (expression.expression.async) { - story.async = true; - } - } if ( expression && (expression.expression.type === 'CallExpression' || diff --git a/core/instrument/test/__snapshots__/esm-async.test.ts.snap b/core/instrument/test/__snapshots__/esm-async.test.ts.snap index effe54591..d8fc6cfd9 100644 --- a/core/instrument/test/__snapshots__/esm-async.test.ts.snap +++ b/core/instrument/test/__snapshots__/esm-async.test.ts.snap @@ -59,7 +59,6 @@ Object { "stories": Object { "myStory": Object { "arguments": Array [], - "async": true, "id": "myStory", "loc": Object { "end": Object { @@ -140,7 +139,6 @@ Object { "stories": Object { "asyncStory": Object { "arguments": Array [], - "async": true, "id": "asyncStory", "loc": Object { "end": Object { @@ -237,7 +235,6 @@ Object { "stories": Object { "myStory": Object { "arguments": Array [], - "async": true, "id": "myStory", "loc": Object { "end": Object { @@ -348,7 +345,6 @@ Object { "value": "props", }, ], - "async": true, "id": "myStory", "loc": Object { "end": Object { diff --git a/core/instrument/test/__snapshots__/mdx-async.test.ts.snap b/core/instrument/test/__snapshots__/mdx-async.test.ts.snap index 5ec74820d..51d0825fa 100644 --- a/core/instrument/test/__snapshots__/mdx-async.test.ts.snap +++ b/core/instrument/test/__snapshots__/mdx-async.test.ts.snap @@ -82,7 +82,6 @@ Object { "value": "props", }, ], - "async": true, "id": "storyWithProps", "loc": Object { "end": Object { diff --git a/core/render/README.md b/core/render/README.md index 979aae86e..bad18a4a5 100644 --- a/core/render/README.md +++ b/core/render/README.md @@ -31,8 +31,8 @@ _defined in [@component-controls/render/src/index.ts](https://github.com/ccontro ### properties -| Name | Type | Description | -| -------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `react*` | **function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): Promise<[ReactElement](#reactelement)>; | | +| Name | Type | Description | +| -------- | ----------------------------------------------------------------------------------------------------------------------- | ----------- | +| `react*` | **function** (`story`\*: [Story](#story), `doc`: [Document](#document), `options`: any): [ReactElement](#reactelement); | | diff --git a/core/render/src/react.ts b/core/render/src/react.ts index 68d63648d..8dc609e80 100644 --- a/core/render/src/react.ts +++ b/core/render/src/react.ts @@ -6,13 +6,7 @@ import { FrameworkRenderFn, } from '@component-controls/core'; -const isPromise = (obj: any) => obj instanceof Promise; - -export const render: FrameworkRenderFn = async ( - story, - doc, - options: any = {}, -) => { +export const render: FrameworkRenderFn = (story, doc, options: any = {}) => { if (!story) { throw new Error(`Invalid story`); } @@ -47,12 +41,6 @@ export const render: FrameworkRenderFn = async ( ); } let node: any = null; - if (renderFn) { - if (story.async || isPromise(renderFn)) { - node = await (renderFn as StoryRenderFn)(values, context); - } else { - node = () => (renderFn as StoryRenderFn)(values, context); - } - } + node = () => (renderFn as StoryRenderFn)(values, context); return createElement(node); }; diff --git a/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap b/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap index 3398e5dcd..4935cb056 100644 --- a/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap +++ b/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap @@ -489,6 +489,16 @@ Button.propTypes = { }, "version": "1.30.0", }, + "872a13aed459690f92f1efc4366b1f3c": Object { + "fileHash": "872a13aed459690f92f1efc4366b1f3c", + "name": "component-controls-stories", + "repository": Object { + "browse": "https://github.com/ccontrols/component-controls/tree/master/examples/stories/src/stories/stories-async.stories.tsx", + "docs": "https://github.com/ccontrols/component-controls/tree/master#readme", + "issues": "https://github.com/ccontrols/component-controls/issues", + }, + "version": "1.30.0", + }, "af3ea9029e5eedf5278b612cda3d978c": Object { "fileHash": "af3ea9029e5eedf5278b612cda3d978c", "name": "component-controls-stories", @@ -519,16 +529,6 @@ Button.propTypes = { }, "version": "1.30.0", }, - "c10c397609217009986a01bedb0da812": Object { - "fileHash": "c10c397609217009986a01bedb0da812", - "name": "component-controls-stories", - "repository": Object { - "browse": "https://github.com/ccontrols/component-controls/tree/master/examples/stories/src/stories_native/async-stories.stories.tsx", - "docs": "https://github.com/ccontrols/component-controls/tree/master#readme", - "issues": "https://github.com/ccontrols/component-controls/issues", - }, - "version": "1.30.0", - }, "dd96934cfa10038428b57421ede5ec17": Object { "fileHash": "dd96934cfa10038428b57421ede5ec17", "name": "component-controls-stories", @@ -561,171 +561,6 @@ Button.propTypes = { }, }, "stores": Array [ - Object { - "doc": Object { - "author": "atanasster", - "components": Object {}, - "componentsLookup": Object {}, - "date": "2020-10-14T06:45:03.199Z", - "dateModified": "2020-10-14T06:45:03.201Z", - "fileName": "/Users/atanasster/component-controls/examples/stories/src/stories_native/async-stories.stories.tsx", - "package": "c10c397609217009986a01bedb0da812", - "title": "Introduction/Async stories", - }, - "filePath": "/Users/atanasster/component-controls/examples/stories/src/stories_native/async-stories.stories.tsx", - "stories": Object { - "asyncDecorators": Object { - "arguments": Array [ - Object { - "loc": Object { - "end": Object { - "column": 2, - "line": 0, - }, - "start": Object { - "column": 1, - "line": 0, - }, - }, - "name": "_", - "value": "_", - }, - Object { - "loc": Object { - "end": Object { - "column": 16, - "line": 0, - }, - "start": Object { - "column": 4, - "line": 0, - }, - }, - "value": Array [ - Object { - "loc": Object { - "end": Object { - "column": 14, - "line": 0, - }, - "start": Object { - "column": 6, - "line": 0, - }, - }, - "name": "employee", - "value": "employee", - }, - ], - }, - ], - "decorators": Array [ - [Function], - ], - "description": "Decorators can also be async functions - if you have storyes that are async, you will need to await call them.", - "id": "asyncDecorators", - "loc": Object { - "end": Object { - "column": 1, - "line": 60, - }, - "start": Object { - "column": 31, - "line": 58, - }, - }, - "name": "asyncDecorators", - "renderFn": [Function], - "source": "(_, { employee }) => { - return

{\`Hello, my name is \${employee.employee_name}.\`}

; -}", - }, - "asyncFunction": Object { - "arguments": Array [], - "async": true, - "description": "Async exported function, can fetch data or other async activity.", - "id": "asyncFunction", - "loc": Object { - "end": Object { - "column": 1, - "line": 26, - }, - "start": Object { - "column": 7, - "line": 20, - }, - }, - "name": "asyncFunction", - "renderFn": [Function], - "source": "async function asyncFunction() { - const response = await fetch( - 'http://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{\`Hello, my name is \${data.employee_name}.\`}

; -}", - }, - "asyncStory": Object { - "arguments": Array [], - "async": true, - "description": "Async story, can *fetch* data or other async activity.", - "id": "asyncStory", - "loc": Object { - "end": Object { - "column": 1, - "line": 15, - }, - "start": Object { - "column": 26, - "line": 9, - }, - }, - "name": "asyncStory", - "renderFn": [Function], - "source": "async () => { - const response = await fetch( - 'http://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{\`Hello, my name is \${data.employee_name}.\`}

; -}", - }, - "hooksStory": Object { - "arguments": Array [], - "decorators": Array [ - [Function], - ], - "description": "Story fetching data with react hooks/useEffect.", - "id": "hooksStory", - "loc": Object { - "end": Object { - "column": 1, - "line": 44, - }, - "start": Object { - "column": 26, - "line": 31, - }, - }, - "name": "hooksStory", - "renderFn": [Function], - "source": "() => { - const [name, setName] = useState(''); - useEffect(() => { - const data = async () => { - const response = await fetch( - 'http://dummy.restapiexample.com/api/v1/employee/1', - ); - const employee = await response.json(); - setName(employee.data.employee_name); - }; - data(); - }, []); - return

{\`Hello, my name is \${name}.\`}

; -}", - }, - }, - }, Object { "doc": Object { "author": "atanasster", @@ -2703,6 +2538,202 @@ any *markdown* is allowed }, }, }, + Object { + "doc": Object { + "author": "atanasster", + "components": Object {}, + "componentsLookup": Object {}, + "date": "2020-10-14T06:45:03.199Z", + "dateModified": "2020-10-14T19:42:21.593Z", + "fileName": "/Users/atanasster/component-controls/examples/stories/src/stories/stories-async.stories.tsx", + "package": "872a13aed459690f92f1efc4366b1f3c", + "title": "Introduction/Async stories", + }, + "filePath": "/Users/atanasster/component-controls/examples/stories/src/stories/stories-async.stories.tsx", + "stories": Object { + "asyncDecorators": Object { + "arguments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 0, + }, + "start": Object { + "column": 1, + "line": 0, + }, + }, + "name": "_", + "value": "_", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 0, + }, + "start": Object { + "column": 4, + "line": 0, + }, + }, + "value": Array [ + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 0, + }, + "start": Object { + "column": 6, + "line": 0, + }, + }, + "name": "employee", + "value": "employee", + }, + ], + }, + ], + "decorators": Array [ + [Function], + ], + "description": "Decorators can also fetch async data using the useAsync hook.", + "id": "asyncDecorators", + "loc": Object { + "end": Object { + "column": 75, + "line": 54, + }, + "start": Object { + "column": 31, + "line": 54, + }, + }, + "name": "asyncDecorators", + "renderFn": [Function], + "source": "(_, { employee }) => storyTemplate(employee)", + }, + "asyncHooksDecorators": Object { + "arguments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 0, + }, + "start": Object { + "column": 1, + "line": 0, + }, + }, + "name": "_", + "value": "_", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 0, + }, + "start": Object { + "column": 4, + "line": 0, + }, + }, + "value": Array [ + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 0, + }, + "start": Object { + "column": 6, + "line": 0, + }, + }, + "name": "employee", + "value": "employee", + }, + ], + }, + ], + "decorators": Array [ + [Function], + ], + "description": "Decorators can also fetch async data using react hooks/useEffect.", + "id": "asyncHooksDecorators", + "loc": Object { + "end": Object { + "column": 25, + "line": 72, + }, + "start": Object { + "column": 36, + "line": 71, + }, + }, + "name": "asyncHooksDecorators", + "renderFn": [Function], + "source": "(_, { employee }) => + storyTemplate(employee)", + }, + "asyncStory": Object { + "arguments": Array [], + "description": "Stories can invoke async operations using the useAsync hook.", + "id": "asyncStory", + "loc": Object { + "end": Object { + "column": 1, + "line": 24, + }, + "start": Object { + "column": 26, + "line": 21, + }, + }, + "name": "asyncStory", + "renderFn": [Function], + "source": "() => { + const { value } = useAsync(fetchData); + return storyTemplate(value); +}", + }, + "hooksStory": Object { + "arguments": Array [], + "decorators": Array [ + [Function], + ], + "description": "Story fetching data with regular react hooks/useEffect.", + "id": "hooksStory", + "loc": Object { + "end": Object { + "column": 1, + "line": 39, + }, + "start": Object { + "column": 26, + "line": 29, + }, + }, + "name": "hooksStory", + "renderFn": [Function], + "source": "() => { + const [employee, setEmployee] = useState(''); + useEffect(() => { + const data = async () => { + const employee = await fetchData(); + setEmployee(employee); + }; + data(); + }, []); + return storyTemplate(employee); +}", + }, + }, + }, Object { "doc": Object { "author": "atanasster", diff --git a/examples/stories/src/stories/stories-async.stories.tsx b/examples/stories/src/stories/stories-async.stories.tsx new file mode 100644 index 000000000..9c48c1593 --- /dev/null +++ b/examples/stories/src/stories/stories-async.stories.tsx @@ -0,0 +1,94 @@ +/* eslint-disable react/display-name */ +import React, { useState, useEffect } from 'react'; +import { useAsync } from '@component-controls/core'; + +export default { + title: 'Introduction/Async stories', + author: 'atanasster', +}; + +const fetchData = async () => { + const response = await fetch('//dummy.restapiexample.com/api/v1/employee/1'); + const { data } = await response.json(); + return data; +}; + +const storyTemplate = employee => ( +

{`Hello, my name is ${ + employee ? employee.employee_name : 'loading...' + }.`}

+); +export const asyncStory = () => { + const { value } = useAsync(fetchData); + return storyTemplate(value); +}; + +asyncStory.description = + 'Stories can invoke async operations using the useAsync hook.'; + +export const hooksStory = () => { + const [employee, setEmployee] = useState(''); + useEffect(() => { + const data = async () => { + const employee = await fetchData(); + setEmployee(employee); + }; + data(); + }, []); + return storyTemplate(employee); +}; + +hooksStory.description = + 'Story fetching data with regular react hooks/useEffect.'; +hooksStory.decorators = [ + (controls, context) => { + const { renderFn } = context; + return ( +
+ {renderFn(controls, context)} +
+ ); + }, +]; + +export const asyncDecorators = (_, { employee }) => storyTemplate(employee); + +asyncDecorators.description = + 'Decorators can also fetch async data using the useAsync hook.'; +asyncDecorators.decorators = [ + (controls, context) => { + const { value } = useAsync(fetchData); + + const { renderFn } = context; + return ( +
+ {renderFn(controls, { ...context, employee: value })} +
+ ); + }, +]; + +export const asyncHooksDecorators = (_, { employee }) => + storyTemplate(employee); + +asyncHooksDecorators.description = + 'Decorators can also fetch async data using react hooks/useEffect.'; +asyncHooksDecorators.decorators = [ + (controls, context) => { + const [employee, setEmployee] = useState({}); + useEffect(() => { + const data = async () => { + const employee = await fetchData(); + setEmployee(employee); + }; + data(); + }, []); + + const { renderFn } = context; + return ( +
+ {renderFn(controls, { ...context, employee })} +
+ ); + }, +]; diff --git a/examples/stories/src/stories_native/async-stories.stories.tsx b/examples/stories/src/stories_native/async-stories.stories.tsx deleted file mode 100644 index 4dde82bba..000000000 --- a/examples/stories/src/stories_native/async-stories.stories.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable react/display-name */ -import React, { useState, useEffect } from 'react'; - -export default { - title: 'Introduction/Async stories', - author: 'atanasster', -}; - -export const asyncStory = async () => { - const response = await fetch( - 'https://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{`Hello, my name is ${data.employee_name}.`}

; -}; - -asyncStory.description = - 'Async story, can *fetch* data or other async activity.'; - -export async function asyncFunction() { - const response = await fetch( - 'https://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{`Hello, my name is ${data.employee_name}.`}

; -} - -asyncFunction.description = - 'Async exported function, can fetch data or other async activity.'; - -export const hooksStory = () => { - const [name, setName] = useState(''); - useEffect(() => { - const data = async () => { - const response = await fetch( - 'https://dummy.restapiexample.com/api/v1/employee/1', - ); - const employee = await response.json(); - setName(employee.data.employee_name); - }; - data(); - }, []); - return

{`Hello, my name is ${name}.`}

; -}; - -hooksStory.description = 'Story fetching data with react hooks/useEffect.'; -hooksStory.decorators = [ - (controls, context) => { - const { renderFn } = context; - return ( -
- {renderFn(controls, context)} -
- ); - }, -]; - -export const asyncDecorators = (_, { employee }) => { - return

{`Hello, my name is ${employee.employee_name}.`}

; -}; - -asyncDecorators.description = - 'Decorators can also be async functions - if you have storyes that are async, you will need to await call them.'; -asyncDecorators.decorators = [ - (controls, context) => { - const [employee, setEmployee] = useState({}); - useEffect(() => { - const data = async () => { - const response = await fetch( - 'https://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - setEmployee(data); - }; - data(); - }, []); - - const { renderFn } = context; - return ( -
- {renderFn(controls, { ...context, employee })} -
- ); - }, -]; diff --git a/examples/stories/src/tutorial/write-documentation/esmodules-stories.mdx b/examples/stories/src/tutorial/write-documentation/esmodules-stories.mdx index 20c7ec983..cebaee3c8 100644 --- a/examples/stories/src/tutorial/write-documentation/esmodules-stories.mdx +++ b/examples/stories/src/tutorial/write-documentation/esmodules-stories.mdx @@ -113,27 +113,24 @@ buttonColors.dynamic = true; ### Async stories -Sometimes, you might need to fetch data in your stories, and component-controls can automatically detect async arrow function or regular functions +Sometimes, you might need to fetch data (or execute some other async activity) in your stories or decorators, and component-controls provides a custom `useAsync` hook for that purpose: ``` -// async arrow function -export const asyncStory = async () => { - const response = await fetch( - 'http://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{`Hello, my name is ${data.employee_name}.`}

; +import { useAsync } from '@component-controls/core'; + +export const asyncFetch = () => { + // async function to fetch data + const fetchData = async () => { + const response = await fetch('//dummy.restapiexample.com/api/v1/employee/1'); + const { data } = await response.json(); + return data; + }; + const { value: employee } = useAsync(fetchData); + return

{`Hello, my name is ${ + employee ? employee.employee_name : 'loading...' + }.`}

; }; - -// async function -export async function asyncFunction() { - const response = await fetch( - 'http://dummy.restapiexample.com/api/v1/employee/1', - ); - const { data } = await response.json(); - return () =>

{`Hello, my name is ${data.employee_name}.`}

; -} ``` [live example](/api/introduction-async-stories--async-story) -> Note that you can also fetch data using react's useEffect hooks, without the need fot async stories. \ No newline at end of file +> Note that you can also fetch data using react's useEffect hooks, without using `useAsync`. \ No newline at end of file diff --git a/plugins/axe-plugin/src/AllyBlock/AllyBlock.tsx b/plugins/axe-plugin/src/AllyBlock/AllyBlock.tsx index 4726dc483..8ce542397 100644 --- a/plugins/axe-plugin/src/AllyBlock/AllyBlock.tsx +++ b/plugins/axe-plugin/src/AllyBlock/AllyBlock.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/display-name */ -import React, { FC, useRef, useContext } from 'react'; +import React, { FC, useRef, useContext, useState, useEffect } from 'react'; import { run as runAxe, configure as configureAxe, reset } from 'axe-core'; import { useStory, StoryInputProps } from '@component-controls/store'; @@ -33,7 +33,12 @@ const RenderStory: FC = ({ }) => { const storyRef = useRef(null); const { setResults } = useContext(AxeSetContext); + const [mounted, setMounted] = useState(true); const isRunning = useRef(false); + useEffect(() => { + setMounted(true); + return () => setMounted(false); + }, []); const collectResults = () => { const canvas = storyRef.current?.firstChild; if (canvas && isRunning.current === false) { @@ -43,9 +48,11 @@ const RenderStory: FC = ({ resetTabCounter(); runAxe(canvas) .then(results => { - const { passes, violations, incomplete } = results; - setResults({ passes, violations, incomplete }); - setTimeout(() => (isRunning.current = false), 1000); + if (mounted) { + const { passes, violations, incomplete } = results; + setResults({ passes, violations, incomplete }); + setTimeout(() => (isRunning.current = false), 1000); + } }) .catch(e => { console.error('error running axe-core', e); diff --git a/plugins/jest-snapshots/src/renderers/enzyme-react-16.ts b/plugins/jest-snapshots/src/renderers/enzyme-react-16.ts index 7e2b916c5..dc8c6b3b2 100644 --- a/plugins/jest-snapshots/src/renderers/enzyme-react-16.ts +++ b/plugins/jest-snapshots/src/renderers/enzyme-react-16.ts @@ -16,7 +16,7 @@ export const render: RendererFn = async ( if (renderFn) { const story = store.stories[storyId]; const doc = story?.doc ? store.docs[story?.doc] : undefined; - const rendered = await renderFn(story, doc, options); + const rendered = renderFn(story, doc, options); const component = mount(rendered); return toJson(component, { mode: 'deep' }); } diff --git a/plugins/jest-snapshots/src/renderers/react-test-renderer.ts b/plugins/jest-snapshots/src/renderers/react-test-renderer.ts index f4daf9cc4..faa1c75e6 100644 --- a/plugins/jest-snapshots/src/renderers/react-test-renderer.ts +++ b/plugins/jest-snapshots/src/renderers/react-test-renderer.ts @@ -11,7 +11,7 @@ export const render: RendererFn = async ( if (renderFn) { const story = store.stories[storyId]; const doc = story?.doc ? store.docs[story?.doc] : undefined; - const rendered = await renderFn(story, doc, options); + const rendered = renderFn(story, doc, options); const component = renderer.create(rendered); return component.toJSON(); } diff --git a/plugins/jest-snapshots/src/renderers/react-testing-library.ts b/plugins/jest-snapshots/src/renderers/react-testing-library.ts index b061c263a..ae04677d2 100644 --- a/plugins/jest-snapshots/src/renderers/react-testing-library.ts +++ b/plugins/jest-snapshots/src/renderers/react-testing-library.ts @@ -1,4 +1,4 @@ -import { render as rtlRender, act } from '@testing-library/react'; +import { render as rtlRender } from '@testing-library/react'; import { Store } from '@component-controls/core'; import { RendererFn } from '../index'; @@ -13,14 +13,9 @@ export const render: RendererFn = async ( const story = store.stories[storyId]; const doc = story?.doc ? store.docs[story?.doc] : undefined; - let asFragment: any; - await act(async () => { - const rendered = await renderFn(story, doc, options); - ({ asFragment } = rtlRender(rendered)); - }); - if (asFragment) { - return asFragment(); - } + const rendered = renderFn(story, doc, options); + const { asFragment } = rtlRender(rendered); + return asFragment(); } return undefined; }; diff --git a/ui/blocks/src/Story/StoryRender.tsx b/ui/blocks/src/Story/StoryRender.tsx index 607824496..363bc773f 100644 --- a/ui/blocks/src/Story/StoryRender.tsx +++ b/ui/blocks/src/Story/StoryRender.tsx @@ -1,5 +1,5 @@ /** @jsx jsx */ -import { FC, useState, useEffect, Fragment, forwardRef } from 'react'; +import { FC, useState, Fragment, forwardRef } from 'react'; import { jsx, CSSProperties, Box } from 'theme-ui'; import Iframe from 'react-frame-component'; @@ -82,22 +82,15 @@ export const StoryRender: FC = forwardRef( { story, wrapper, iframeStyle, ...rest }, ref: React.Ref, ) => { - const [renderedStory, setRenderedStory] = useState(null); const store = useStore(); const options = useExternalOptions(); - useEffect(() => { - const asyncFn = async () => { - const rendered = store.config.renderFn - ? await store.config.renderFn( - story, - story.doc ? store.docs[story.doc] : undefined, - options, - ) - : null; - setRenderedStory(rendered); - }; - asyncFn(); - }, [options, ref, store.config, store.docs, story]); + const rendered = store.config.renderFn + ? store.config.renderFn( + story, + story.doc ? store.docs[story.doc] : undefined, + options, + ) + : null; return ( = forwardRef( variant={`${NAME}.wrapper`} ref={ref} > - {renderedStory} + {rendered}