Comparing version 1.0.0-alpha.15 to 1.0.0
@@ -0,3 +1,7 @@ | ||
<a name="1.0.0"></a> | ||
# 1.0.0 (Feb 17th, 2020) | ||
* Caching previously created components to avoid re-rendering them and losing store state | ||
<a name="1.0.0-alpha.13"></a> | ||
# 1.0.0-alpha.13 (2019-14-06) | ||
# 1.0.0-alpha.13 (June 14th, 2019) | ||
* Fixed examples, renamed from units to stores | ||
@@ -7,3 +11,3 @@ * Added support for debugName for Macro | ||
<a name="1.0.0-alpha.12"></a> | ||
# 1.0.0-alpha.12 (2019-11-06) | ||
# 1.0.0-alpha.12 (June 11th, 2019) | ||
* Version announced at ReactNext |
@@ -1,2 +0,2 @@ | ||
var e=require("react"),t=function(e){this.fn=e,this.name="Store",this.subscribers=[],this.cachedValue=null};t.prototype.getValue=function(){return this.cachedValue},t.prototype.run=function(){return this.cachedValue=this.fn(),this.cachedValue},t.prototype.subscribe=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},t.prototype.notify=function(){var e=this;this.subscribers.forEach(function(t){return t(e.cachedValue)})};var r=function(){this.stores=new Map,this.subscribers=[]};r.prototype.onStoresChanged=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},r.prototype.createStore=function(e){if(this.stores.has(e))throw new Error("Store already exist");var r=new t(e);this.stores.set(e,r),this.notifyStoresChanged()},r.prototype.getStore=function(e){if(!this.stores.has(e))throw new Error("Store doesn't exist");return this.stores.get(e)},r.prototype.notifyStoresChanged=function(){this.subscribers.forEach(function(e){return e()})},r.prototype.getStoresArray=function(){var e=[];return this.stores.forEach(function(t){e.push(t)}),e};var n=new r,o=function(){return n},u=Object.prototype.hasOwnProperty;function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function i(e,t){if(s(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(var o=0;o<r.length;o++)if(!u.call(t,r[o])||!s(e[r[o]],t[r[o]]))return!1;return!0}var c=e.createContext(null),a=function(t){var r=t.children;return e.createElement(c.Provider,{value:o()},e.createElement(e.Fragment,null,e.createElement(l,null),r))};Object.defineProperty(a,"displayName",{value:"ReusableProvider"});var f=function(){var t=e.useContext(c);if(null===t)throw new Error("Are you trying to use Reusable without a ReusableProvider?");return t},l=function(){var t=f(),r=e.useState(function(){return t.getStoresArray()}),n=r[0],o=r[1];return e.useEffect(function(){return t.onStoresChanged(function(){o(t.getStoresArray())})},[]),e.createElement(e.Fragment,null,n.map(function(t,r){var n,o=(n=function(t){var r=t.store;return r.run(),e.useDebugValue(r.name),e.useEffect(function(){return r.notify()},[r.cachedValue]),null},Object.defineProperty(n,"name",{value:t.name}),n);return e.createElement(o,{key:r,store:t})}))};Object.defineProperty(l,"displayName",{value:"Stores"});var h=function(e){return e};exports.Store=t,exports.Container=r,exports.createContainer=function(){return new r},exports.getContainer=o,exports.replaceContainer=function(e){return n=e},exports.ReusableProvider=a,exports.createStore=function(t){return o().createStore(t),function(r,n){return function(t,r,n){void 0===r&&(r=h),void 0===n&&(n=i);var o=f().getStore(t),u=e.useState(function(){return r(o.getValue())}),s=u[0],c=u[1];return e.useEffect(function(){return o.subscribe(function(e){var t=r(e);n(t,s)||c(function(){return t})})},[o,s,r,n]),s}(t,r,n)}}; | ||
var e=require("react"),t=function(e){this.fn=e,this.subscribers=[],this.cachedValue=null,this.name=e.name||"Store"};t.prototype.getCachedValue=function(){return this.cachedValue},t.prototype.useValue=function(){return this.cachedValue=this.fn(),this.cachedValue},t.prototype.subscribe=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},t.prototype.notify=function(){var e=this;this.subscribers.forEach(function(t){return t(e.cachedValue)})};var r=function(){this.stores=new Map,this.subscribers=[]};r.prototype.onStoresChanged=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},r.prototype.createStore=function(e){if(this.stores.has(e))throw new Error("Store already exist");var r=new t(e);return this.stores.set(e,r),this.notifyStoresChanged(),r},r.prototype.getStore=function(e){if(!this.stores.has(e))throw new Error("Store doesn't exist");return this.stores.get(e)},r.prototype.notifyStoresChanged=function(){this.subscribers.forEach(function(e){return e()})},r.prototype.getStoresArray=function(){var e=[];return this.stores.forEach(function(t){e.push(t)}),e};var n=new r,u=function(){return n},o=Object.prototype.hasOwnProperty;function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function i(e,t){if(s(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(var u=0;u<r.length;u++)if(!o.call(t,r[u])||!s(e[r[u]],t[r[u]]))return!1;return!0}var a=e.createContext(null),c=function(t){var r=t.children;return e.createElement(a.Provider,{value:u()},e.createElement(e.Fragment,null,e.createElement(h,null),r))};Object.defineProperty(c,"displayName",{value:"ReusableProvider"});var f=new Map,l=function(){var t=e.useContext(a);if(null===t)throw new Error("Are you trying to use Reusable without a ReusableProvider?");return t},h=function(){var t=l(),r=e.useState(function(){return t.getStoresArray()}),n=r[0],u=r[1];return e.useEffect(function(){return t.onStoresChanged(function(){u(t.getStoresArray())})},[]),e.createElement(e.Fragment,null,n.map(function(t,r){var n=function(t){if(!f.has(t)){var r=e.memo(function(){return t.useValue(),e.useEffect(function(){return t.notify()},[t.cachedValue]),null});Object.defineProperty(r,"name",{value:t.name}),f.set(t,r)}return f.get(t)}(t);return e.createElement(n,{key:r,store:t})}))};Object.defineProperty(h,"displayName",{value:"Stores"});var b=function(e){return e};exports.Store=t,exports.Container=r,exports.createContainer=function(){return new r},exports.getContainer=u,exports.replaceContainer=function(e){return n=e},exports.ReusableProvider=c,exports.createStore=function(t){var r=u().createStore(t);return function(n,u){return e.useDebugValue(r.name),function(t,r,n){var u=l().getStore(t);e.useDebugValue("reusable");var o=e.useState(function(){return r(u.getCachedValue())}),s=o[0],i=o[1];return e.useEffect(function(){return u.subscribe(function(e){var t=r(e);n(t,s)||i(function(){return t})})},[u,s,r,n]),s}(t,n||b,u||i)}}; | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t(e.reusable={},e.react)}(this,function(e,t){var r=function(e){this.fn=e,this.name="Store",this.subscribers=[],this.cachedValue=null};r.prototype.getValue=function(){return this.cachedValue},r.prototype.run=function(){return this.cachedValue=this.fn(),this.cachedValue},r.prototype.subscribe=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},r.prototype.notify=function(){var e=this;this.subscribers.forEach(function(t){return t(e.cachedValue)})};var n=function(){this.stores=new Map,this.subscribers=[]};n.prototype.onStoresChanged=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},n.prototype.createStore=function(e){if(this.stores.has(e))throw new Error("Store already exist");var t=new r(e);this.stores.set(e,t),this.notifyStoresChanged()},n.prototype.getStore=function(e){if(!this.stores.has(e))throw new Error("Store doesn't exist");return this.stores.get(e)},n.prototype.notifyStoresChanged=function(){this.subscribers.forEach(function(e){return e()})},n.prototype.getStoresArray=function(){var e=[];return this.stores.forEach(function(t){e.push(t)}),e};var o=new n,u=function(){return o},i=Object.prototype.hasOwnProperty;function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function c(e,t){if(s(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(var o=0;o<r.length;o++)if(!i.call(t,r[o])||!s(e[r[o]],t[r[o]]))return!1;return!0}var a=t.createContext(null),f=function(e){var r=e.children;return t.createElement(a.Provider,{value:u()},t.createElement(t.Fragment,null,t.createElement(h,null),r))};Object.defineProperty(f,"displayName",{value:"ReusableProvider"});var l=function(){var e=t.useContext(a);if(null===e)throw new Error("Are you trying to use Reusable without a ReusableProvider?");return e},h=function(){var e=l(),r=t.useState(function(){return e.getStoresArray()}),n=r[0],o=r[1];return t.useEffect(function(){return e.onStoresChanged(function(){o(e.getStoresArray())})},[]),t.createElement(t.Fragment,null,n.map(function(e,r){var n,o=(n=function(e){var r=e.store;return r.run(),t.useDebugValue(r.name),t.useEffect(function(){return r.notify()},[r.cachedValue]),null},Object.defineProperty(n,"name",{value:e.name}),n);return t.createElement(o,{key:r,store:e})}))};Object.defineProperty(h,"displayName",{value:"Stores"});var b=function(e){return e};e.Store=r,e.Container=n,e.createContainer=function(){return new n},e.getContainer=u,e.replaceContainer=function(e){return o=e},e.ReusableProvider=f,e.createStore=function(e){return u().createStore(e),function(r,n){return function(e,r,n){void 0===r&&(r=b),void 0===n&&(n=c);var o=l().getStore(e),u=t.useState(function(){return r(o.getValue())}),i=u[0],s=u[1];return t.useEffect(function(){return o.subscribe(function(e){var t=r(e);n(t,i)||s(function(){return t})})},[o,i,r,n]),i}(e,r,n)}}}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t(e.reusable={},e.react)}(this,function(e,t){var r=function(e){this.fn=e,this.subscribers=[],this.cachedValue=null,this.name=e.name||"Store"};r.prototype.getCachedValue=function(){return this.cachedValue},r.prototype.useValue=function(){return this.cachedValue=this.fn(),this.cachedValue},r.prototype.subscribe=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},r.prototype.notify=function(){var e=this;this.subscribers.forEach(function(t){return t(e.cachedValue)})};var n=function(){this.stores=new Map,this.subscribers=[]};n.prototype.onStoresChanged=function(e){var t=this;return this.subscribers=this.subscribers.concat([e]),function(){t.subscribers=t.subscribers.filter(function(t){return t!==e})}},n.prototype.createStore=function(e){if(this.stores.has(e))throw new Error("Store already exist");var t=new r(e);return this.stores.set(e,t),this.notifyStoresChanged(),t},n.prototype.getStore=function(e){if(!this.stores.has(e))throw new Error("Store doesn't exist");return this.stores.get(e)},n.prototype.notifyStoresChanged=function(){this.subscribers.forEach(function(e){return e()})},n.prototype.getStoresArray=function(){var e=[];return this.stores.forEach(function(t){e.push(t)}),e};var u=new n,o=function(){return u},i=Object.prototype.hasOwnProperty;function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function a(e,t){if(s(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(var u=0;u<r.length;u++)if(!i.call(t,r[u])||!s(e[r[u]],t[r[u]]))return!1;return!0}var c=t.createContext(null),f=function(e){var r=e.children;return t.createElement(c.Provider,{value:o()},t.createElement(t.Fragment,null,t.createElement(b,null),r))};Object.defineProperty(f,"displayName",{value:"ReusableProvider"});var l=new Map,h=function(){var e=t.useContext(c);if(null===e)throw new Error("Are you trying to use Reusable without a ReusableProvider?");return e},b=function(){var e=h(),r=t.useState(function(){return e.getStoresArray()}),n=r[0],u=r[1];return t.useEffect(function(){return e.onStoresChanged(function(){u(e.getStoresArray())})},[]),t.createElement(t.Fragment,null,n.map(function(e,r){var n=function(e){if(!l.has(e)){var r=t.memo(function(){return e.useValue(),t.useEffect(function(){return e.notify()},[e.cachedValue]),null});Object.defineProperty(r,"name",{value:e.name}),l.set(e,r)}return l.get(e)}(e);return t.createElement(n,{key:r,store:e})}))};Object.defineProperty(b,"displayName",{value:"Stores"});var p=function(e){return e};e.Store=r,e.Container=n,e.createContainer=function(){return new n},e.getContainer=o,e.replaceContainer=function(e){return u=e},e.ReusableProvider=f,e.createStore=function(e){var r=o().createStore(e);return function(n,u){return t.useDebugValue(r.name),function(e,r,n){var u=h().getStore(e);t.useDebugValue("reusable");var o=t.useState(function(){return r(u.getCachedValue())}),i=o[0],s=o[1];return t.useEffect(function(){return u.subscribe(function(e){var t=r(e);n(t,i)||s(function(){return t})})},[u,i,r,n]),i}(e,n||p,u||a)}}}); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -5,4 +5,7 @@ import { FunctionComponent } from 'react'; | ||
export declare const ReusableProvider: FunctionComponent<{}>; | ||
declare type SelectorFn<HookValue> = (val: HookValue) => any; | ||
export declare function createStore<HookValue>(fn: HookFn<HookValue>): (selector?: SelectorFn<HookValue> | undefined, areEqual?: AreEqual<HookValue> | undefined) => any; | ||
declare type SelectorFn<HookValue, SelectorValue> = (val: HookValue) => SelectorValue; | ||
export declare function createStore<HookValue>(fn: HookFn<HookValue>): { | ||
(): HookValue; | ||
<SelectorValue = HookValue>(selector?: SelectorFn<HookValue, SelectorValue> | undefined, areEqual?: AreEqual<SelectorValue> | undefined): SelectorValue; | ||
}; | ||
export {}; |
@@ -1,5 +0,5 @@ | ||
export declare type HookFn<HookValue> = () => HookValue; | ||
export declare type StoreValueChangeCallback<HookValue> = (value: HookValue | null) => void; | ||
export declare type HookFn<HookValue = any> = () => HookValue; | ||
export declare type StoreValueChangeCallback<HookValue> = (value: HookValue) => void; | ||
export declare type StoresChangeCallback = () => void; | ||
export declare class Store<HookValue> { | ||
export declare class Store<HookValue = any> { | ||
private fn; | ||
@@ -10,4 +10,4 @@ name: string; | ||
constructor(fn: HookFn<HookValue>); | ||
getValue(): HookValue | null; | ||
run(): HookValue; | ||
getCachedValue(): HookValue; | ||
useValue(): HookValue; | ||
subscribe(callback: StoreValueChangeCallback<HookValue>): () => void; | ||
@@ -20,4 +20,4 @@ notify(): void; | ||
onStoresChanged(callback: StoresChangeCallback): () => void; | ||
createStore(fn: HookFn<any>): void; | ||
getStore(fn: HookFn<any>): Store<any>; | ||
createStore(fn: HookFn): Store<any>; | ||
getStore<HookValue>(fn: HookFn<HookValue>): Store<HookValue>; | ||
notifyStoresChanged(): void; | ||
@@ -24,0 +24,0 @@ getStoresArray(): Store<any>[]; |
@@ -1,2 +0,2 @@ | ||
export declare type AreEqual<HookValue> = (a: HookValue, b: HookValue) => boolean; | ||
export declare type AreEqual<Value> = (a: Value, b: Value) => boolean; | ||
export declare function shallowEqual(objA: any, objB: any): boolean; |
{ | ||
"name": "reusable", | ||
"version": "1.0.0-alpha.15", | ||
"version": "1.0.0", | ||
"description": "", | ||
@@ -30,2 +30,3 @@ "keywords": [], | ||
"jest": "^24.8.0", | ||
"js-yaml": "^3.13.1", | ||
"lodash": "^4.17.11", | ||
@@ -39,2 +40,3 @@ "microbundle": "^0.11.0", | ||
"react-test-renderer": "^16.8.6", | ||
"relative-deps": "^0.2.0", | ||
"ts-jest": "^24.0.2" | ||
@@ -41,0 +43,0 @@ }, |
[![Build Status](https://circleci.com/gh/reusablejs/reusable.svg?style=svg)](https://circleci.com/gh/reusablejs/reusable) | ||
[![npm version](https://badge.fury.io/js/reusable.svg)](https://badge.fury.io/js/reusable) | ||
# What?! | ||
# Reusable | ||
<img src="https://github.com/reusablejs/reusable/blob/master/website/static/img/reusable.png?raw=true" width="120"/> | ||
Reusable is a solution for state management, based on React Hooks. | ||
It allows you to transform your custom hooks to singleton stores, and subscribe to them directly from any component. | ||
The benefits: | ||
- Stores are decoupled from component tree | ||
- You can use selectors and bail out of render | ||
- Stores can co-depend without worrying about provider order | ||
- It's easy to create packages that rely on global state, and plug them into apps seamlessly | ||
# Show me the code! | ||
Wrap your app with a provider: | ||
```javascript | ||
const App = () => ( | ||
<ReusableProvider> {/* no initialization code, stores automatically plug into the top provider */} | ||
... | ||
</ReusableProvider> | ||
) | ||
``` | ||
# Custom hook => Store | ||
Pass your custom hooks to `createStore`: | ||
And just pass your custom hooks to `createStore`: | ||
```javascript | ||
@@ -38,3 +25,3 @@ const useCounter = createStore(() => { | ||
Then you get a singleton store, and a hook that subscribes directly to that store: | ||
and get a singleton store, with a hook that subscribes directly to that store: | ||
```javascript | ||
@@ -50,2 +37,11 @@ const Comp1 = () => { | ||
Then wrap your app with a provider: | ||
```javascript | ||
const App = () => ( | ||
<ReusableProvider> {/* no initialization code, stores automatically plug into the top provider */} | ||
... | ||
</ReusableProvider> | ||
) | ||
``` | ||
## Selectors | ||
@@ -70,3 +66,3 @@ You can also use selectors, and your component will re-render only if the return value changed: | ||
const currentUser = useCurrentUser(); | ||
const posts = useCurrentUser(); | ||
const posts = usePosts(); | ||
@@ -77,13 +73,39 @@ return ... | ||
# Docs | ||
Check out the full docs here: | ||
[https://reusablejs.github.io/reusable](https://reusablejs.github.io/reusable) | ||
# Check out the video | ||
Check out the video where Maayan Glikser and Adam Klein announce the library for the first time and explain all about the problems it aims to solve and how it works: | ||
[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/oy-6urveWzo/0.jpg)](https://www.youtube.com/watch?v=oy-6urveWzo) | ||
# The benefits | ||
- Stores are decoupled from component tree | ||
- You can use selectors and bail out of render | ||
- Stores can co-depend without worrying about provider order | ||
- It's easy to create packages that rely on global state, and plug them into apps seamlessly | ||
# Why do we need (yet) another state management library? | ||
Most current state management solutions don't let you manage state using hooks, which causes you to manage local and global state differently, and have a costly transition between the two. | ||
Reusable solves this by seemingly transforming your custom hooks into global stores. | ||
# Global Stores?! | ||
Sounds like an anti-pattern, but in fact decoupling your state management solution from your component tree gives the developers a lot of flexibilty while designing both the component tree, and the app's state. | ||
# What about hooks+Context? | ||
Using plain context is not a best solution for state management, that led us to write this library: | ||
Using plain context has some drawbacks and limitations, that led us to write this library: | ||
- Context doesn't support selectors, render bailout, or debouncing | ||
- When managing global state using Context in a large app, you will probably have many small, single-purpose providers. Soon enough you'll find a Provider wrapper hell. | ||
- When you order the providers vertically, you can’t dynamically choose to depend on each other without changing the order, which might break things. | ||
- Context doesn't support selectors, render bailout, or debouncing | ||
# How it works? | ||
React hooks must run inside a component, and our store is based on a custom hook. | ||
So in order to have a store that uses a custom hook, we need to create a component for each of our stores. | ||
The `ReusableProvider` component renders a `Stores` component, under which it will render one component per store, which only runs the store's hook, and renders nothing to the DOM. Then, it uses an effect to update all subscribers with the new value. | ||
Notice that the `ReusableProvider` uses a Context provider at the top-level, but it provides a stable ref that never changes. This means that changing store values, and even dynamically adding stores won't re-render your app. | ||
# Docs | ||
Check out the docs here: | ||
[https://reusablejs.github.io/reusable/docs/basic-usage.html](https://reusablejs.github.io/reusable/docs/basic-usage.html) | ||
## Feedback / Contributing: | ||
@@ -90,0 +112,0 @@ We would love your feedback / suggestions |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
82582
22
162
5
111
22