From 500c23cf43803329cea4c0838fe6d3f8277e7062 Mon Sep 17 00:00:00 2001 From: raozixuan Date: Thu, 7 Oct 2021 16:11:16 +0800 Subject: [PATCH 1/3] feat: draft implementation of useStore in #733 (not production ready) Co-authored-by: pablopalacios --- packages/fluxible-addons-react/src/index.js | 1 + .../fluxible-addons-react/src/useStore.js | 36 +++++++++++++++++++ .../tests/unit/lib/useStore.js | 30 ++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 packages/fluxible-addons-react/src/useStore.js create mode 100644 packages/fluxible-addons-react/tests/unit/lib/useStore.js diff --git a/packages/fluxible-addons-react/src/index.js b/packages/fluxible-addons-react/src/index.js index 79143eb4..f4360b19 100644 --- a/packages/fluxible-addons-react/src/index.js +++ b/packages/fluxible-addons-react/src/index.js @@ -11,3 +11,4 @@ export { default as createElementWithContext } from './createElementWithContext' export { default as provideContext } from './provideContext'; export { default as useFluxible } from './useFluxible'; export { default as withFluxible } from './withFluxible'; +export { default as useStore } from './useStore'; diff --git a/packages/fluxible-addons-react/src/useStore.js b/packages/fluxible-addons-react/src/useStore.js new file mode 100644 index 00000000..d257b528 --- /dev/null +++ b/packages/fluxible-addons-react/src/useStore.js @@ -0,0 +1,36 @@ +import {useEffect, useState} from 'react'; +import useFluxible from './useFluxible' + +/** + * React hook that returns a state from 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); + * return

; + * }; + * + * @function useFluxible + * @returns {object} - a state from Fluxible store + */ +const useStore = (storeName, getStateFromStore) => { + const { getStore } = useFluxible(); + const store = getStore(storeName); + const [state, setState] = useState(getStateFromStore(store)); + + function updateState() { + setState(getStateFromStore(store)); + } + + useEffect(() => { + store.on('change', updateState); + return () => store.removeListener('change', updateState); + }, [store, updateState]); + + return state; +} + +export default useStore; diff --git a/packages/fluxible-addons-react/tests/unit/lib/useStore.js b/packages/fluxible-addons-react/tests/unit/lib/useStore.js new file mode 100644 index 00000000..9ca2bb52 --- /dev/null +++ b/packages/fluxible-addons-react/tests/unit/lib/useStore.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { expect } from 'chai'; +import TestRenderer from 'react-test-renderer'; +import createMockComponentContext from 'fluxible/utils/createMockComponentContext'; +import FooStore from '../../fixtures/stores/FooStore'; +import { useStore, FluxibleProvider } from '../../../'; + +describe('fluxible-addons-react', () => { + describe('useStore', () => { + it('returns fluxible store', () => { + const FooComponent = () => { + const getStateFromStore = store => store.getFoo(); + const foo = useStore('FooStore', getStateFromStore); + return

; + }; + + const context = createMockComponentContext({ stores: [FooStore] }); + + const testRenderer = TestRenderer.create( + + + + ); + + const component = testRenderer.root.findByType('p'); + + expect(component.props.id).to.deep.equal('bar'); + }); + }); +}); From e415fe71d3b27258f6c398ff58ba675753ecfece Mon Sep 17 00:00:00 2001 From: Zixuan Rao Date: Tue, 12 Oct 2021 18:30:57 +0800 Subject: [PATCH 2/3] 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); + }); }); }); From c4bdc96f47d656305199e690419dea71b9d93488 Mon Sep 17 00:00:00 2001 From: billyrrr Date: Wed, 13 Oct 2021 22:04:14 +0800 Subject: [PATCH 3/3] feat: fix useStore --- .../src/useExecuteAction.js | 2 +- .../fluxible-addons-react/src/useStore.js | 16 +++++++-------- .../tests/unit/lib/useStore.js | 20 +++++++++++++++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/fluxible-addons-react/src/useExecuteAction.js b/packages/fluxible-addons-react/src/useExecuteAction.js index 0674871c..228b813f 100644 --- a/packages/fluxible-addons-react/src/useExecuteAction.js +++ b/packages/fluxible-addons-react/src/useExecuteAction.js @@ -11,7 +11,7 @@ import useFluxible from './useFluxible' * return

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

; * }; * - * @function usetStore - * @returns {object} - Fluxible store + * @function useStore + * @returns {object} - a state from Fluxible store */ -const useStore = (storeName,) => { +const useStore = (storeName, getStateFromStore) => { const { getStore } = useFluxible(); const store = getStore(storeName); - const [state, setState] = useState(store); + const [state, setState] = useState(getStateFromStore(store)); function updateState() { - setState(store); + setState(getStateFromStore(store)); } // useLayoutEffect is the closest to componentDidMount @@ -30,7 +30,7 @@ const useStore = (storeName,) => { useLayoutEffect(() => { store.on('change', updateState); return () => store.removeListener('change', updateState); - }, [store, updateState]); + }, []); return state; } diff --git a/packages/fluxible-addons-react/tests/unit/lib/useStore.js b/packages/fluxible-addons-react/tests/unit/lib/useStore.js index 0d0d46bd..d5a7d6cd 100644 --- a/packages/fluxible-addons-react/tests/unit/lib/useStore.js +++ b/packages/fluxible-addons-react/tests/unit/lib/useStore.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { expect } from 'chai'; import TestRenderer from 'react-test-renderer'; import createMockComponentContext from 'fluxible/utils/createMockComponentContext'; @@ -8,8 +8,8 @@ import BarStore from '../../fixtures/stores/BarStore'; const DumbComponent = () => { - const foo = useStore(FooStore).getFoo(); - const bar = useStore(BarStore).getBar(); + const foo = useStore(FooStore, store => store.getFoo()); + const bar = useStore(BarStore, store => store.getBar()); const executeAction = useExecuteAction(); const onClick = () => executeAction((context) => context.dispatch('DOUBLE_UP')) @@ -43,7 +43,7 @@ describe('fluxible-addons-react', () => { describe('useStore', () => { it('returns fluxible store', () => { const FooComponent = () => { - const foo = useStore('FooStore').getFoo(); + const foo = useStore('FooStore', store => store.getFoo()) return

; }; @@ -73,5 +73,17 @@ describe('fluxible-addons-react', () => { expect(barStore.listeners('change').length).to.equal(0); expect(fooStore.listeners('change').length).to.equal(0); }); + it('should listen to store changes', () => { + const { app } = renderComponent(DumbComponent); + const button = app.root.findByProps({ id: 'button' }) + const foo = app.root.findByProps({ id: 'foo' }) + const bar = app.root.findByProps({ id: 'bar' }) + + button.props.onClick(); + console.error(foo) + + expect(foo.props.children).to.equal('barbar'); + expect(bar.props.children).to.equal('bazbaz'); + }); }); });