From 28d4a034c96b396018debe60b6ffe2de39d78c20 Mon Sep 17 00:00:00 2001 From: atanasster Date: Mon, 20 Jan 2020 16:07:47 -0500 Subject: [PATCH 1/2] api: adding useSharedState, useStoryState --- examples/dev-kits/manager.js | 10 +++- .../dev-kits/stories/addon-usestorystate.tsx | 33 ++++++++++++ lib/api/src/index.tsx | 51 +++++++++++-------- lib/client-api/src/hooks.ts | 31 +++++++---- lib/core-events/src/index.ts | 8 +-- 5 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 examples/dev-kits/stories/addon-usestorystate.tsx diff --git a/examples/dev-kits/manager.js b/examples/dev-kits/manager.js index 15fdfd4baa6d..027b5032041d 100644 --- a/examples/dev-kits/manager.js +++ b/examples/dev-kits/manager.js @@ -2,7 +2,7 @@ import React from 'react'; import { PropTypes } from 'prop-types'; import { Button } from '@storybook/react/demo'; import { addons } from '@storybook/addons'; -import { useAddonState } from '@storybook/api'; +import { useAddonState, useStoryState } from '@storybook/api'; import { themes } from '@storybook/theming'; import { AddonPanel } from '@storybook/components'; @@ -15,6 +15,7 @@ addons.setConfig({ const StatePanel = ({ active, key }) => { const [managerState, setManagerState] = useAddonState('manager', 10); const [previewState, setPreviewState] = useAddonState('preview'); + const [storyState, setstoryState] = useStoryState(10); return (
@@ -30,6 +31,13 @@ const StatePanel = ({ active, key }) => {
+
+
+ Story counter: {storyState} +
+ + +
); }; diff --git a/examples/dev-kits/stories/addon-usestorystate.tsx b/examples/dev-kits/stories/addon-usestorystate.tsx new file mode 100644 index 000000000000..ded51bae63e5 --- /dev/null +++ b/examples/dev-kits/stories/addon-usestorystate.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Button } from '@storybook/react/demo'; +import { useStoryState } from '@storybook/client-api'; + +export default { + title: 'addons|useAddonState', +}; + +export const storyState1 = () => { + const [state, setState] = useStoryState(10); + + return ( +
+ Story counter: {state} +
+ + +
+ ); +}; + +export const storyState2 = () => { + const [state, setState] = useStoryState(10); + + return ( +
+ Story counter: {state} +
+ + +
+ ); +}; diff --git a/lib/api/src/index.tsx b/lib/api/src/index.tsx index e4ce7ef1cd47..ea6cf9900ff9 100644 --- a/lib/api/src/index.tsx +++ b/lib/api/src/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, Component, useContext, useEffect, useMemo } from 'react'; +import React, { ReactElement, Component, useContext, useEffect, useMemo, useRef } from 'react'; import memoize from 'memoizerific'; // @ts-ignore shallow-equal is not in DefinitelyTyped import shallowEqualObjects from 'shallow-equal/objects'; @@ -8,8 +8,8 @@ import { STORY_CHANGED, SET_STORIES, SELECT_STORY, - ADDON_STATE_CHANGED, - ADDON_STATE_SET, + SHARED_STATE_CHANGED, + SHARED_STATE_SET, NAVIGATE_URL, } from '@storybook/core-events'; import { RenderData as RouterData } from '@storybook/router'; @@ -345,42 +345,42 @@ const addonStateCache: { } = {}; // shared state -export function useAddonState(addonId: string, defaultState?: S) { +export function useSharedState(stateId: string, defaultState?: S) { const api = useStorybookApi(); - const existingState = api.getAddonState(addonId); + const existingState = api.getAddonState(stateId); const state = orDefault( existingState, - addonStateCache[addonId] ? addonStateCache[addonId] : defaultState + addonStateCache[stateId] ? addonStateCache[stateId] : defaultState ); const setState = (s: S | StateMerger, options?: Options) => { // set only after the stories are loaded - if (addonStateCache[addonId]) { - addonStateCache[addonId] = s; + if (addonStateCache[stateId]) { + addonStateCache[stateId] = s; } - api.setAddonState(addonId, s, options); + api.setAddonState(stateId, s, options); }; const allListeners = useMemo(() => { const stateChangeHandlers = { - [`${ADDON_STATE_CHANGED}-client-${addonId}`]: (s: S) => setState(s), - [`${ADDON_STATE_SET}-client-${addonId}`]: (s: S) => setState(s), + [`${SHARED_STATE_CHANGED}-client-${stateId}`]: (s: S) => setState(s), + [`${SHARED_STATE_SET}-client-${stateId}`]: (s: S) => setState(s), }; const stateInitializationHandlers = { [STORIES_CONFIGURED]: () => { - if (addonStateCache[addonId]) { + if (addonStateCache[stateId]) { // this happens when HMR - setState(addonStateCache[addonId]); - api.emit(`${ADDON_STATE_SET}-manager-${addonId}`, addonStateCache[addonId]); + setState(addonStateCache[stateId]); + api.emit(`${SHARED_STATE_SET}-manager-${stateId}`, addonStateCache[stateId]); } else if (defaultState !== undefined) { // if not HMR, yet the defaults are form the manager setState(defaultState); // initialize addonStateCache after first load, so its available for subsequent HMR - addonStateCache[addonId] = defaultState; - api.emit(`${ADDON_STATE_SET}-manager-${addonId}`, defaultState); + addonStateCache[stateId] = defaultState; + api.emit(`${SHARED_STATE_SET}-manager-${stateId}`, defaultState); } }, [STORY_CHANGED]: () => { - if (api.getAddonState(addonId) !== undefined) { - api.emit(`${ADDON_STATE_SET}-manager-${addonId}`, api.getAddonState(addonId)); + if (api.getAddonState(stateId) !== undefined) { + api.emit(`${SHARED_STATE_SET}-manager-${stateId}`, api.getAddonState(stateId)); } }, }; @@ -389,14 +389,25 @@ export function useAddonState(addonId: string, defaultState?: S) { ...stateChangeHandlers, ...stateInitializationHandlers, }; - }, [addonId]); + }, [stateId]); const emit = useChannel(allListeners); return [ state, (newStateOrMerger: S | StateMerger, options?: Options) => { setState(newStateOrMerger, options); - emit(`${ADDON_STATE_CHANGED}-manager-${addonId}`, newStateOrMerger); + emit(`${SHARED_STATE_CHANGED}-manager-${stateId}`, newStateOrMerger); }, ] as [S, (newStateOrMerger: S | StateMerger, options?: Options) => void]; } + +export function useAddonState(addonId: string, defaultState?: S) { + return useSharedState(addonId, defaultState); +} + +export function useStoryState(defaultState?: S) { + const { + state: { storyId }, + } = useContext(ManagerContext); + return useSharedState(`story-state-${storyId}`, defaultState); +} diff --git a/lib/client-api/src/hooks.ts b/lib/client-api/src/hooks.ts index 05d6776031a6..651928660d67 100644 --- a/lib/client-api/src/hooks.ts +++ b/lib/client-api/src/hooks.ts @@ -1,4 +1,4 @@ -import { ADDON_STATE_CHANGED, ADDON_STATE_SET } from '@storybook/core-events'; +import { SHARED_STATE_CHANGED, SHARED_STATE_SET } from '@storybook/core-events'; import { addons, @@ -29,38 +29,47 @@ export { useParameter, }; -export function useAddonState(addonId: string, defaultState?: S): [S, (s: S) => void] { +export function useSharedState(sharedId: string, defaultState?: S): [S, (s: S) => void] { const channel = addons.getChannel(); const [lastValue] = - channel.last(`${ADDON_STATE_CHANGED}-manager-${addonId}`) || - channel.last(`${ADDON_STATE_SET}-manager-${addonId}`) || + channel.last(`${SHARED_STATE_CHANGED}-manager-${sharedId}`) || + channel.last(`${SHARED_STATE_SET}-manager-${sharedId}`) || []; const [state, setState] = useState(lastValue || defaultState); const allListeners = useMemo( () => ({ - [`${ADDON_STATE_CHANGED}-manager-${addonId}`]: (s: S) => setState(s), - [`${ADDON_STATE_SET}-manager-${addonId}`]: (s: S) => setState(s), + [`${SHARED_STATE_CHANGED}-manager-${sharedId}`]: (s: S) => setState(s), + [`${SHARED_STATE_SET}-manager-${sharedId}`]: (s: S) => setState(s), }), - [addonId] + [sharedId] ); - const emit = useChannel(allListeners, [addonId]); + const emit = useChannel(allListeners, [sharedId]); useEffect(() => { // init if (defaultState !== undefined && !lastValue) { - emit(`${ADDON_STATE_SET}-client-${addonId}`, defaultState); + emit(`${SHARED_STATE_SET}-client-${sharedId}`, defaultState); } - }, [addonId]); + }, [sharedId]); return [ state, s => { setState(s); - emit(`${ADDON_STATE_CHANGED}-client-${addonId}`, s); + emit(`${SHARED_STATE_CHANGED}-client-${sharedId}`, s); }, ]; } + +export function useAddonState(addonId: string, defaultState?: S): [S, (s: S) => void] { + return useSharedState(addonId, defaultState); +} + +export function useStoryState(defaultState?: S): [S, (s: S) => void] { + const { id: storyId } = useStoryContext(); + return useSharedState(`story-state-${storyId}`, defaultState); +} diff --git a/lib/core-events/src/index.ts b/lib/core-events/src/index.ts index 84c12831f68d..ef62ec54f093 100644 --- a/lib/core-events/src/index.ts +++ b/lib/core-events/src/index.ts @@ -21,8 +21,8 @@ enum events { STORIES_COLLAPSE_ALL = 'storiesCollapseAll', STORIES_EXPAND_ALL = 'storiesExpandAll', DOCS_RENDERED = 'docsRendered', - ADDON_STATE_CHANGED = 'addonStateChanged', - ADDON_STATE_SET = 'addonStateSet', + SHARED_STATE_CHANGED = 'sharedStateChanged', + SHARED_STATE_SET = 'sharedStateSet', NAVIGATE_URL = 'navigateUrl', } @@ -53,7 +53,7 @@ export const { STORIES_EXPAND_ALL, STORY_THREW_EXCEPTION, DOCS_RENDERED, - ADDON_STATE_CHANGED, - ADDON_STATE_SET, + SHARED_STATE_CHANGED, + SHARED_STATE_SET, NAVIGATE_URL, } = events; From d4a3e3003a74f40a1608b8d6126e44a590592bbc Mon Sep 17 00:00:00 2001 From: atanasster Date: Mon, 20 Jan 2020 16:21:31 -0500 Subject: [PATCH 2/2] useStorybookState instead of useContext --- lib/api/src/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/api/src/index.tsx b/lib/api/src/index.tsx index ea6cf9900ff9..4758cddfc9c9 100644 --- a/lib/api/src/index.tsx +++ b/lib/api/src/index.tsx @@ -406,8 +406,6 @@ export function useAddonState(addonId: string, defaultState?: S) { } export function useStoryState(defaultState?: S) { - const { - state: { storyId }, - } = useContext(ManagerContext); + const { storyId } = useStorybookState(); return useSharedState(`story-state-${storyId}`, defaultState); }