Skip to content

Commit

Permalink
feat: useStore and useExecuteAction
Browse files Browse the repository at this point in the history
  • Loading branch information
billyrrr committed Oct 12, 2021
1 parent 500c23c commit e415fe7
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/fluxible-addons-react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
22 changes: 22 additions & 0 deletions packages/fluxible-addons-react/src/useExecuteAction.js
Original file line number Diff line number Diff line change
@@ -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 <p id={foo} onClick={() => excuteAction(...)} />;
* };
*
* @function useFluxible
* @returns {Function} - executeAction handler
*/
const useExecuteAction = () => {
const { executeAction } = useFluxible();
return executeAction;
}

export default useExecuteAction;
22 changes: 12 additions & 10 deletions packages/fluxible-addons-react/src/useStore.js
Original file line number Diff line number Diff line change
@@ -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 <p id={foo} />;
* };
*
* @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]);
Expand Down
53 changes: 50 additions & 3 deletions packages/fluxible-addons-react/tests/unit/lib/useStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<span id="foo">{foo}</span>
<span id="bar">{bar}</span>
<button id="button" onClick={onClick} />
</div>
)
};

DumbComponent.displayName = 'DumbComponent';
DumbComponent.initAction = () => {};

const stores = [FooStore, BarStore];

const renderComponent = (Component) => {
const context = createMockComponentContext({ stores });

const app = TestRenderer.create(
<FluxibleComponent context={context}>
<Component ref={undefined} />
</FluxibleComponent>
);

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 <p id={foo} />;
};

Expand All @@ -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);
});
});
});

0 comments on commit e415fe7

Please sign in to comment.