Skip to content

Commit

Permalink
Store provider migration and rework (#91)
Browse files Browse the repository at this point in the history
* add store provider variation for testing, add config for array syntax

* update counter examples to work with non-array syntax

* rename provider functions

* update readme doc

* update docs and version

* update spec

* fix other spec

* create unique store instance for the dynamic store example

* fix store spec import

* compact the new store a bit more

* more cleanup
  • Loading branch information
chaceburnette authored Jan 28, 2022
1 parent 5682a5e commit e5c407a
Show file tree
Hide file tree
Showing 14 changed files with 84 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist/
node_modules/

*.swp
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## v2.0.0
* BREAKING CHANGE - When subscribing to a store via the `useContext` hook, the returning store object is now a single object which represents the store. Previously this was an array, but the array was arbitrary.
* chore/bug - Reworked store providers to correctly target rerenders only to subscribing children components. Previously there was a nuance where updates could occur even without a context subscription.

## v1.3.0
* feature - support multiple dynamic stores and forwarding props into the store custom hooks
* chore - fix persisted falsy value causing default value to come back
Expand Down
2 changes: 1 addition & 1 deletion examples/counter-react/src/__tests__/counter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('Counter', () => {
decrementCount: jest.fn()
};
let ContextComponent = () => (
<CounterStore.Context.Provider value={[counterStore]}>
<CounterStore.Context.Provider value={counterStore}>
<Counter />
</CounterStore.Context.Provider>
);
Expand Down
8 changes: 4 additions & 4 deletions examples/counter-react/src/counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import React, { useContext } from 'react';
import { CounterStore } from './store';

const Counter = () => {
const [counterContext] = useContext(CounterStore.Context);
let { count } = counterContext.state;
const counterStore = useContext(CounterStore.Context);
let { count } = counterStore.state;

return (
<div data-testid="counter-wrap">
<p>Count: {count}</p>
<button data-testid="decrement" onClick={counterContext.decrementCount}>
<button data-testid="decrement" onClick={counterStore.decrementCount}>
-
</button>
<button data-testid="increment" onClick={counterContext.incrementCount}>
<button data-testid="increment" onClick={counterStore.incrementCount}>
+
</button>
</div>
Expand Down
12 changes: 6 additions & 6 deletions examples/counter-react/src/counterByName.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React, { useContext } from 'react';
import { CounterStore } from './store';
import { DynamicCounterStore } from './store';

const Counter = ({ name }) => {
const [counterContext] = useContext(CounterStore.Context);
let { count } = counterContext.state;
const counterStore = useContext(DynamicCounterStore.Context);
let { count } = counterStore.state;

// this isn't necessary, but this demonstrates how a store ref would be used if a storeKey is provided
const incrementCount = () => CounterStore[name].incrementCount();
const incrementCount = () => DynamicCounterStore[name].incrementCount();

return (
<div data-testid="counter-wrap">
<p>
Counter with key {name}: {count}
</p>
<button data-testid="decrement" onClick={counterContext.decrementCount}>
<button data-testid="decrement" onClick={counterStore.decrementCount}>
-
</button>
<button data-testid="increment" onClick={incrementCount}>
Expand All @@ -23,4 +23,4 @@ const Counter = ({ name }) => {
);
};

export default CounterStore.Provider(Counter);
export default DynamicCounterStore.Provider(Counter);
6 changes: 3 additions & 3 deletions examples/counter-react/src/dispatchCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React, { useContext } from 'react';
import { CounterWithReducerStore } from './store';

const PersistedCounter = () => {
const [counterContext] = useContext(CounterWithReducerStore.Context);
let { dispatch, counterState: { count }} = counterContext;
const counterStore = useContext(CounterWithReducerStore.Context);
let { dispatch, counterState: { count }} = counterStore;

return (
<div data-testid="counter-wrap">
Expand All @@ -18,4 +18,4 @@ const PersistedCounter = () => {
);
};

export default PersistedCounter;
export default PersistedCounter;
8 changes: 4 additions & 4 deletions examples/counter-react/src/persistedCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import React, { useContext } from 'react';
import { CounterStore } from './store';

const PersistedCounter = () => {
const [counterContext] = useContext(CounterStore.Context);
let { persistedCount } = counterContext.state;
const counterStore = useContext(CounterStore.Context);
let { persistedCount } = counterStore.state;

return (
<div data-testid="counter-wrap">
<p>Persisted Count: {persistedCount}</p>
<button data-testid="decrement" onClick={counterContext.decrementPersistedCount}>
<button data-testid="decrement" onClick={counterStore.decrementPersistedCount}>
-
</button>
<button data-testid="increment" onClick={counterContext.incrementPersistedCount}>
<button data-testid="increment" onClick={counterStore.incrementPersistedCount}>
+
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useContext } from 'react';
import CounterStore from '../counter.store';
import { CounterStore } from '../counter.store';
import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';

describe('CounterStore', () => {
let store;
const renderStore = () => {
let Prep = CounterStore.Provider(() => {
store = useContext(CounterStore.Context)[0];
store = useContext(CounterStore.Context);
return null;
});
render(<Prep />);
Expand Down
3 changes: 2 additions & 1 deletion examples/counter-react/src/store/counter.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ const useCounterContainer = () => {
};

let CounterStore = setupStore(useCounterContainer);
let DynamicCounterStore = setupStore(useCounterContainer);

export default CounterStore ;
export { CounterStore, DynamicCounterStore };
2 changes: 1 addition & 1 deletion examples/counter-react/src/store/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as CounterStore } from './counter.store';
export { CounterStore, DynamicCounterStore } from './counter.store';
export { default as CounterWithReducerStore } from './counterWithReducer.store.js';
2 changes: 1 addition & 1 deletion osmosis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ import React, { useContext } from 'react';
import { CounterStore } from './counter.store';

export default () => {
const [counterStore] = useContext(CounterStore.Context);
const counterStore = useContext(CounterStore.Context);
let { count } = counterStore.state;

return (
Expand Down
2 changes: 1 addition & 1 deletion osmosis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shipt/osmosis",
"version": "1.3.0",
"version": "2.0.0",
"description": "A lightweight state management library for React and React Native",
"keywords": [
"react",
Expand Down
5 changes: 3 additions & 2 deletions osmosis/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { StoreProvider } from './storeProvider.js';
import { setupStore } from './setupStore.js';
import { setupStore, configureSetupStore } from './setupStore.js';
import { usePersistedState, configureUsePersistedState } from './usePersistedState.js';

export { setupStore, StoreProvider, usePersistedState, configureUsePersistedState };
export { setupStore, configureSetupStore, StoreProvider, usePersistedState, configureUsePersistedState };
export default {
setupStore,
configureSetupStore,
StoreProvider,
usePersistedState,
configureUsePersistedState
Expand Down
54 changes: 50 additions & 4 deletions osmosis/src/setupStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* eslint-disable react/prop-types */
import React, { createContext } from 'react';

const _defaultConfig = { proxyEnabled: true };
let _defaultConfig = { proxyEnabled: true, legacyReturnStoreAsArray: false };

export const configureSetupStore = config => {
_defaultConfig = { ..._defaultConfig, ...config };
};

/**
* @callback useCustomHook
Expand All @@ -26,7 +31,8 @@ const _defaultConfig = { proxyEnabled: true };
* @param {SetupStoreConfig} [config = { proxyEnabled: false }] - The setup store config
* @returns {Store}
*/
const setupStore = (useCustomHook, config = _defaultConfig) => {
const setupStore = (useCustomHook, config = {}) => {
config = { ..._defaultConfig, ...config };
const StoreContext = createContext();
// If proxy is not supported
let storeRef = {};
Expand All @@ -35,7 +41,8 @@ const setupStore = (useCustomHook, config = _defaultConfig) => {
let storeProxy;
let storeProxyObject = { ref: {} };

const withStoreContext = WrappedComponent => props => {
// Legacy Store Provider
const withLegacyStoreContext = WrappedComponent => props => {
let storeKey = props.storeKey;
let store = useCustomHook(props);
if (!!store.Context) throw new Error("'Context' property is protected and cannot exist on a store object");
Expand All @@ -55,25 +62,64 @@ const setupStore = (useCustomHook, config = _defaultConfig) => {
}
}

const value = config.legacyReturnStoreAsArray ? [store] : store;

return (
<StoreContext.Provider value={[store]}>
<StoreContext.Provider value={value}>
<WrappedComponent {...props} />
</StoreContext.Provider>
);
};

// New Store Provider
const withStoreContext = WrappedComponent => {
const StoreContextWrapper = ({ children, ...props }) => {
let storeKey = props.storeKey;
let store = useCustomHook(props);
if (!!store.Context) throw new Error("'Context' property is protected and cannot exist on a store object");
if (!!store.Provider) throw new Error("'Provider' property is protected and cannot exist on a store object");

if (storeProxy) {
if (storeKey) {
storeProxyObject.ref[storeKey] = store;
} else storeProxyObject.ref = store;
} else {
if (storeKey) {
storeRef[storeKey] = store;
} else {
for (let key in store) {
storeRef[key] = store[key];
}
}
}

const value = config.legacyReturnStoreAsArray ? [store] : store;

return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>;
};

const Wrapper = props => (
<StoreContextWrapper {...props}>
<WrappedComponent {...props} />
</StoreContextWrapper>
);
return Wrapper;
};

if (!!Proxy && config.proxyEnabled) {
storeProxy = new Proxy(storeProxyObject, {
get: (target, property) => {
if (property === 'Context') return StoreContext;
if (property === 'Provider') return withStoreContext;
if (property === 'LegacyProvider') return withLegacyStoreContext;
return target.ref[property];
},
set: (target, property, value) => (target.ref[property] = value)
});
} else {
storeRef.Context = StoreContext;
storeRef.Provider = withStoreContext;
storeRef.LegacyProvider = withLegacyStoreContext;
}

return storeProxy || storeRef;
Expand Down

0 comments on commit e5c407a

Please sign in to comment.