diff --git a/.gitignore b/.gitignore index 1eae0cf..858ec3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ dist/ node_modules/ + +*.swp diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f8754..16141bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/counter-react/src/__tests__/counter.spec.js b/examples/counter-react/src/__tests__/counter.spec.js index f921b7e..301100b 100644 --- a/examples/counter-react/src/__tests__/counter.spec.js +++ b/examples/counter-react/src/__tests__/counter.spec.js @@ -12,7 +12,7 @@ describe('Counter', () => { decrementCount: jest.fn() }; let ContextComponent = () => ( - + ); diff --git a/examples/counter-react/src/counter.js b/examples/counter-react/src/counter.js index 4863867..966e404 100644 --- a/examples/counter-react/src/counter.js +++ b/examples/counter-react/src/counter.js @@ -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 (

Count: {count}

- -
diff --git a/examples/counter-react/src/counterByName.js b/examples/counter-react/src/counterByName.js index 56d3162..b405181 100644 --- a/examples/counter-react/src/counterByName.js +++ b/examples/counter-react/src/counterByName.js @@ -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 (

Counter with key {name}: {count}

- -
diff --git a/examples/counter-react/src/store/__tests__/counter.store.spec.js b/examples/counter-react/src/store/__tests__/counter.store.spec.js index a9c0ab0..bfd282c 100644 --- a/examples/counter-react/src/store/__tests__/counter.store.spec.js +++ b/examples/counter-react/src/store/__tests__/counter.store.spec.js @@ -1,5 +1,5 @@ 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'; @@ -7,7 +7,7 @@ describe('CounterStore', () => { let store; const renderStore = () => { let Prep = CounterStore.Provider(() => { - store = useContext(CounterStore.Context)[0]; + store = useContext(CounterStore.Context); return null; }); render(); diff --git a/examples/counter-react/src/store/counter.store.js b/examples/counter-react/src/store/counter.store.js index bcc0c09..43e71ba 100644 --- a/examples/counter-react/src/store/counter.store.js +++ b/examples/counter-react/src/store/counter.store.js @@ -28,5 +28,6 @@ const useCounterContainer = () => { }; let CounterStore = setupStore(useCounterContainer); +let DynamicCounterStore = setupStore(useCounterContainer); -export default CounterStore ; +export { CounterStore, DynamicCounterStore }; diff --git a/examples/counter-react/src/store/index.js b/examples/counter-react/src/store/index.js index d1a6e1e..3f7111b 100644 --- a/examples/counter-react/src/store/index.js +++ b/examples/counter-react/src/store/index.js @@ -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'; diff --git a/osmosis/README.md b/osmosis/README.md index a8633fa..eea0dfd 100644 --- a/osmosis/README.md +++ b/osmosis/README.md @@ -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 ( diff --git a/osmosis/package.json b/osmosis/package.json index ebcc324..b999e9b 100644 --- a/osmosis/package.json +++ b/osmosis/package.json @@ -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", diff --git a/osmosis/src/index.js b/osmosis/src/index.js index 233b0a5..3323665 100644 --- a/osmosis/src/index.js +++ b/osmosis/src/index.js @@ -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 diff --git a/osmosis/src/setupStore.js b/osmosis/src/setupStore.js index 0642fc9..31216e0 100644 --- a/osmosis/src/setupStore.js +++ b/osmosis/src/setupStore.js @@ -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 @@ -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 = {}; @@ -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"); @@ -55,18 +62,56 @@ const setupStore = (useCustomHook, config = _defaultConfig) => { } } + const value = config.legacyReturnStoreAsArray ? [store] : store; + return ( - + ); }; + // 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 {children}; + }; + + const Wrapper = props => ( + + + + ); + 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) @@ -74,6 +119,7 @@ const setupStore = (useCustomHook, config = _defaultConfig) => { } else { storeRef.Context = StoreContext; storeRef.Provider = withStoreContext; + storeRef.LegacyProvider = withLegacyStoreContext; } return storeProxy || storeRef;