From e415fe71d3b27258f6c398ff58ba675753ecfece Mon Sep 17 00:00:00 2001 From: Zixuan Rao Date: Tue, 12 Oct 2021 18:30:57 +0800 Subject: [PATCH] feat: useStore and useExecuteAction --- packages/fluxible-addons-react/src/index.js | 1 + .../src/useExecuteAction.js | 22 ++++++++ .../fluxible-addons-react/src/useStore.js | 22 ++++---- .../tests/unit/lib/useStore.js | 53 +++++++++++++++++-- 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 packages/fluxible-addons-react/src/useExecuteAction.js diff --git a/packages/fluxible-addons-react/src/index.js b/packages/fluxible-addons-react/src/index.js index f4360b19..64a7bfcd 100644 --- a/packages/fluxible-addons-react/src/index.js +++ b/packages/fluxible-addons-react/src/index.js @@ -12,3 +12,4 @@ export { default as provideContext } from './provideContext'; export { default as useFluxible } from './useFluxible'; export { default as withFluxible } from './withFluxible'; export { default as useStore } from './useStore'; +export { default as useExecuteAction } from './useExecuteAction'; diff --git a/packages/fluxible-addons-react/src/useExecuteAction.js b/packages/fluxible-addons-react/src/useExecuteAction.js new file mode 100644 index 00000000..0674871c --- /dev/null +++ b/packages/fluxible-addons-react/src/useExecuteAction.js @@ -0,0 +1,22 @@ +import useFluxible from './useFluxible' + +/** + * React hook that returns an executeAction handler + * TODO: this is a draft for an ongoing discussion in #733 + * + * Example: + * + * const FooComponent = () => { + * const executeAction = useExecuteAction(); + * return

excuteAction(...)} />; + * }; + * + * @function useFluxible + * @returns {Function} - executeAction handler + */ +const useExecuteAction = () => { + const { executeAction } = useFluxible(); + return executeAction; +} + +export default useExecuteAction; diff --git a/packages/fluxible-addons-react/src/useStore.js b/packages/fluxible-addons-react/src/useStore.js index d257b528..9278a5ee 100644 --- a/packages/fluxible-addons-react/src/useStore.js +++ b/packages/fluxible-addons-react/src/useStore.js @@ -1,31 +1,33 @@ -import {useEffect, useState} from 'react'; +import {useEffect, useState, useLayoutEffect} from 'react'; import useFluxible from './useFluxible' /** - * React hook that returns a state from Fluxible store. + * React hook that returns a Fluxible store. * TODO: this is a draft for an ongoing discussion in #733 * * Example: * * const FooComponent = () => { - * const getStateFromStore = store => store.getFoo(); - * const foo = useStore('FooStore', getStateFromStore); + * const foo = useStore('FooStore', getStateFromStore).getFoo(); * return

; * }; * - * @function useFluxible - * @returns {object} - a state from Fluxible store + * @function usetStore + * @returns {object} - Fluxible store */ -const useStore = (storeName, getStateFromStore) => { +const useStore = (storeName,) => { const { getStore } = useFluxible(); const store = getStore(storeName); - const [state, setState] = useState(getStateFromStore(store)); + const [state, setState] = useState(store); function updateState() { - setState(getStateFromStore(store)); + setState(store); } - useEffect(() => { + // useLayoutEffect is the closest to componentDidMount + // (we want to block render until store is subscribed) + // TODO: NOTE useLayoutEffect is called on server-side during SSR + useLayoutEffect(() => { store.on('change', updateState); return () => store.removeListener('change', updateState); }, [store, updateState]); diff --git a/packages/fluxible-addons-react/tests/unit/lib/useStore.js b/packages/fluxible-addons-react/tests/unit/lib/useStore.js index 9ca2bb52..0d0d46bd 100644 --- a/packages/fluxible-addons-react/tests/unit/lib/useStore.js +++ b/packages/fluxible-addons-react/tests/unit/lib/useStore.js @@ -2,15 +2,48 @@ import React from 'react'; import { expect } from 'chai'; import TestRenderer from 'react-test-renderer'; import createMockComponentContext from 'fluxible/utils/createMockComponentContext'; +import { useStore, FluxibleProvider, useExecuteAction, FluxibleComponent } from '../../../'; import FooStore from '../../fixtures/stores/FooStore'; -import { useStore, FluxibleProvider } from '../../../'; +import BarStore from '../../fixtures/stores/BarStore'; + +const DumbComponent = () => { + + const foo = useStore(FooStore).getFoo(); + const bar = useStore(BarStore).getBar(); + const executeAction = useExecuteAction(); + const onClick = () => executeAction((context) => context.dispatch('DOUBLE_UP')) + + return ( +

+ {foo} + {bar} +
+ ) +}; + +DumbComponent.displayName = 'DumbComponent'; +DumbComponent.initAction = () => {}; + +const stores = [FooStore, BarStore]; + +const renderComponent = (Component) => { + const context = createMockComponentContext({ stores }); + + const app = TestRenderer.create( + + + + ); + + return { app, context }; +}; describe('fluxible-addons-react', () => { describe('useStore', () => { it('returns fluxible store', () => { const FooComponent = () => { - const getStateFromStore = store => store.getFoo(); - const foo = useStore('FooStore', getStateFromStore); + const foo = useStore('FooStore').getFoo(); return

; }; @@ -26,5 +59,19 @@ describe('fluxible-addons-react', () => { expect(component.props.id).to.deep.equal('bar'); }); + it('should register/unregister from stores on mount/unmount', () => { + const { app, context } = renderComponent(DumbComponent); + + const barStore = context.getStore(BarStore); + const fooStore = context.getStore(FooStore); + + expect(barStore.listeners('change').length).to.equal(1); + expect(fooStore.listeners('change').length).to.equal(1); + + app.unmount(); + + expect(barStore.listeners('change').length).to.equal(0); + expect(fooStore.listeners('change').length).to.equal(0); + }); }); });