diff --git a/.storybook/addons.js b/.storybook/addons.js new file mode 100644 index 0000000..e69de29 diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 0000000..d874ab8 --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,4 @@ +import { configure } from '@storybook/react'; + +// automatically import all files ending in *.stories.js|mdx +configure(require.context('../stories', true, /\.stories\.js|mdx$/), module); diff --git a/.storybook/presets.js b/.storybook/presets.js new file mode 100644 index 0000000..6948513 --- /dev/null +++ b/.storybook/presets.js @@ -0,0 +1,10 @@ +module.exports = [ + { + name: '@storybook/addon-docs/preset', + options: { + configureJSX: true, + babelOptions: {}, + sourceLoaderOptions: null + } + } +]; \ No newline at end of file diff --git a/README.md b/README.md index 781c1c2..27ec26d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # React Hook Store [![npm version](https://badge.fury.io/js/react-hookstore.svg)](https://badge.fury.io/js/react-hookstore) [![Build Status](https://travis-ci.org/jhonnymichel/react-hookstore.svg?branch=master)](https://travis-ci.org/jhonnymichel/react-hookstore) [![Coverage Status](https://coveralls.io/repos/github/jhonnymichel/react-hookstore/badge.svg?branch=master)](https://coveralls.io/github/jhonnymichel/react-hookstore?branch=master) -A very simple and small (1k gzipped!) state management lib for React that uses the bleeding edge React's `useState` hook. -Which basically means no magic behind the curtains, only pure react APIs being used to share state across components. +A very simple and small (less than 2k gzipped!) state management lib for React using hooks. Try it on [Codesandbox!](https://codesandbox.io/s/r58pqonkop) @@ -11,7 +10,8 @@ Try it on [Codesandbox!](https://codesandbox.io/s/r58pqonkop) - Usage - [Basic](#usage_basic) - [Referencing stores](#usage_namespace) - - [Reducer powered stores](#usage_reducer) + - [Using reducers to update state](#usage_reducer) + - [Optimizing performance](#optimizing_performance) - [More examples](https://codesandbox.io/s/r58pqonkop) - API - [createStore](#api_createStore) @@ -30,9 +30,7 @@ You can install the lib through NPM or grab the files in the `dist` folder of th ## Usage ### Basic -This is the most basic implementation of the library. create a store with its initial state. -Later, call `useStore` inside components to retrieve its state and setState method. -The value passed as the first argument to the setState method will be the new state. no reducer required (but you can use a reducer, see the advanced example down below). +This is the most basic implementation of the library: ```javascript import React from 'react'; @@ -41,7 +39,6 @@ import { createStore, useStore } from 'react-hookstore'; createStore('clickStore', 0); function StatefullHello() { - // just use the useStore method to grab the state and the setState methods const [ timesClicked, setClicks ] = useStore('clickStore'); return ( @@ -54,7 +51,6 @@ function StatefullHello() { } function AnotherComponent() { - // you can name the state whatever you want const [ timesClicked ] = useStore('clickStore'); return (
@@ -64,9 +60,13 @@ function AnotherComponent() { ) } ``` +Steps to reproduce: + +- Create a store with its initial state. +- Later, call `useStore` inside components to retrieve its state and setState method, that we called timesClicked and setClicks. +- The value passed as the first argument to the setClicks method will be the new state. ### Referencing stores -It is possible to create multiple stores in an app. Stores can be referenced by using their instance that is returned by the createStore method, as well as using their name. ```javascript @@ -95,10 +95,10 @@ function StatefullHello() { ); } ``` -Both methods can be used and mixed according to the needs, but we recomend using the instance identifiers. +Both methods can be used and mixed according to the needs, but it is recomended to use the instance identifiers. -### Reducer powered stores -We can delegate the state management to reducers (just like redux!) if we want. +### Using reducers to update state +We can delegate the state management to reducers (just like redux!) if we want: ```javascript import React from 'react'; import { createStore, useStore } from 'react-hookstore'; @@ -168,49 +168,60 @@ function TodoList() { export { TodoList, AddTodo }; ``` + +Steps to reproduce: + +- Create a store with an aditional third parameter: a reducer function. +- Later, call `useStore` inside components to retrieve its state and dispatch method. +- call dispatch and provide data as the first argument. Although data can be anything, we are using the pattern of `{ type, payload }`, made popular by redux. + +### Optimizing performance + ### More examples Check out the [Codesandbox demo!](https://codesandbox.io/s/r58pqonkop) ## Methods API -### `createStore(name:String, state?:*, reducer?:Function):StoreInterface` +### `createStore(name, state?, reducer?) -> StoreInterface` Creates a store to be used across the entire application. Returns a StoreInterface object. ### Arguments -#### `name:String` +#### `name: String` The namespace for your store, it can be used to identify the store across the application. -#### `state:* = {}` +#### `state: any = {}` The store's initial state. it can be any data type. defaults to an empty object. Optional -#### `reducer:Function` -You can specify a reducer function to take care of state changes. the reducer functions receives two arguments, the previous state and the action that triggered the state update. the function must return a new state, if not, the new state will be `null`. Optional +#### `reducer: (state:any, data:any) -> any` +You can specify a reducer function to take care of state changes. the reducer functions receives two arguments, the previous state and the data dispatched. the function must return a new state, if not, the new state will be `null`. Optional -### `getStoreByName(name:String):StoreInterface` +### `getStoreByName(name) -> StoreInterface` Finds a store by its name and returns its instance. ### Arguments -#### `name:String` +#### `name: String` The name of the store. ## Objects API ### `StoreInterface` The store instance that is returned by the createStore and getStoreByName methods. ### Interface -#### `name:String` +#### `name: String` The name of the store; -#### `getState:Function():*` +#### `getState: () -> state` A method that returns the store's current state -#### `setState:Function(state:*, callback?:Function)` +#### `setState: (state: any, callback?: (state: any) -> void)` Sets the state of the store. works if the store does not use a reducer state handler. Otherwise, use `dispatch`. callback is optional and will be invoked once the state is updated, receiving the updated state as argument. -#### `dispatch:Function(action:*, callback?:Function)` -Dispatches whatever is passed into this function to the store. works if the store uses a reducer state handler. Otherwise, use `setState`. callback is optional and will be invoked once the state is updated, receiving the updated state as argument. -#### `subscribe:Function(callback:Function):unsubscribe:Function` -The callback function will be invoked everytime the store state changes. If the store is reducer-based, the callback function will be called with `action` as the first argument and `state` as the second. otherwise, it'll be called with `state` as the only argument. +#### `dispatch(data: any, callback?: (state: any) -> void)` +Dispatches data to update the state. works if the store uses a reducer state handler. Otherwise, use `setState`. callback is optional and will be invoked once the state is updated, receiving the updated state as argument. +#### `subscribe(callback: (state: any, data?: any) -> void) -> unsubscribe: () -> void` +The callback function will be invoked everytime the store state changes. If the store is reducer-based, the callback function will be called with the state and the dispatched data as arguments. otherwise, it'll be called with state as the only argument. the subscribe method returns a function that can be called in order to cancel the subscription for the callback function. ## React API -### `useStore(identifier:String|StoreInterface):Array[state, setState|dispatch]` +### `useStore(identifier, memoFn?) -> [state, setState|dispatch]` A function that returns a pair with the current state and a function to trigger state updates for the specified store. ### Arguments -#### Identifier:String|StoreInterface +#### `Identifier: String|StoreInterface` The store identifier. It can be either its string name or its StoreInterface instance returned by a createStore or getStoreByName method. +#### `memoFn: (state) -> any` +A function to optimize performance. return the subset of the state the component is dependent on. The component will only be updated when the subset changes. Optional. # Migrating from v1.0 to v1.1 - createStore now receives 3 arguments instead of an object with 3 properties. @@ -221,11 +232,11 @@ createStore({state: 0}); createStore({ name: 'store', state: 0, - reducer(state, action) { - return state + action; + reducer(state, data) { + return state + data; } }) // v1.1 createStore('myStore', 0); -createStore('store', 0, (state, value) => state + action); +createStore('store', 0, (state, data) => state + data); ``` diff --git a/example/basic.js b/dev-server/basic.js similarity index 100% rename from example/basic.js rename to dev-server/basic.js diff --git a/example/index.html b/dev-server/index.html similarity index 100% rename from example/index.html rename to dev-server/index.html diff --git a/example/index.js b/dev-server/index.js similarity index 100% rename from example/index.js rename to dev-server/index.js diff --git a/example/reducers.js b/dev-server/reducers.js similarity index 100% rename from example/reducers.js rename to dev-server/reducers.js diff --git a/example/subscribe.js b/dev-server/subscribe.js similarity index 100% rename from example/subscribe.js rename to dev-server/subscribe.js diff --git a/dist/react-hookstore.js b/dist/react-hookstore.js index 1726047..a831fe7 100644 --- a/dist/react-hookstore.js +++ b/dist/react-hookstore.js @@ -136,6 +136,10 @@ var subscriptions = {}; var defaultReducer = function defaultReducer(state, payload) { return payload; }; + +var defaultMemoFn = function defaultMemoFn(state) { + return state; +}; /** The public interface of a store */ @@ -156,15 +160,10 @@ function () { } /** * Subscribe to store changes - * @callback callback - The function to be invoked everytime the store is updated + * @param {(state:any, data:any) => void} callback - The function to be invoked everytime the store is updated * @return {Function} - Call the function returned by the method to cancel the subscription */ - /** - * - * @param {callback} state, action - */ - _createClass(StoreInterface, [{ key: "subscribe", @@ -189,14 +188,24 @@ function () { }); }; } + /** + * Set the store state + * @param {any} data - The new state value. + */ + }, { key: "setState", - value: function setState() { + value: function setState(data) { console.warn("[React Hookstore] Store ".concat(this.name, " uses a reducer to handle its state updates. use dispatch instead of setState")); } + /** + * Dispatch data to the store reducer + * @param {any} data - The data payload the reducer receives + */ + }, { key: "dispatch", - value: function dispatch() { + value: function dispatch(data) { console.warn("[React Hookstore] Store ".concat(this.name, " does not use a reducer to handle state updates. use setState instead of dispatch")); } }]); @@ -217,15 +226,10 @@ function getStoreByIdentifier(identifier) { * Creates a new store * @param {String} name - The store namespace. * @param {*} state [{}] - The store initial state. It can be of any type. - * @callback reducer [null] + * @param {(state:any, data:any) => any} reducer [null] - The reducer handler. Optional * @returns {StoreInterface} The store instance. */ -/** - * - * @param {reducer} prevState, action - The reducer handler. Optional. - */ - function createStore(name) { var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; @@ -242,33 +246,72 @@ function createStore(name) { var store = { state: state, reducer: reducer, - setState: function setState(action, callback) { + setState: function setState(data, callback) { var _this2 = this; - this.state = this.reducer(this.state, action); - this.setters.forEach(function (setter) { - return setter(_this2.state); + var isPrimitiveStateWithoutReducerAndIsPreviousState = this.reducer === defaultReducer && data === this.state && _typeof(data) !== 'object'; + + if (isPrimitiveStateWithoutReducerAndIsPreviousState) { + if (typeof callback === 'function') callback(this.state); + return; + } + + var currentState = this.state; + var newState = this.reducer(this.state, data); + this.state = newState; + this.updatersPerMemoFunction.forEach(function (updaters, memoFn) { + var prevResult = memoFn(currentState); + var newResult = memoFn(newState); + + if (prevResult === newResult) { + return; + } + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = updaters[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var updateComponent = _step.value; + updateComponent(_this2.state); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } }); if (subscriptions[name].length) { subscriptions[name].forEach(function (c) { - return c(_this2.state, action); + return c(_this2.state, data); }); } if (typeof callback === 'function') callback(this.state); }, - setters: [] + updatersPerMemoFunction: new Map() }; store.setState = store.setState.bind(store); + store.updatersPerMemoFunction.set(defaultMemoFn, new Set()); + stores = Object.assign({}, stores, _defineProperty({}, name, store)); subscriptions[name] = []; store.public = new StoreInterface(name, store, reducer !== defaultReducer); - stores = Object.assign({}, stores, _defineProperty({}, name, store)); return store.public; } /** * Returns a store instance based on its name - * @callback {String} name - The name of the wanted store + * @name {String} name - The name of the wanted store * @returns {StoreInterface} the store instance */ @@ -282,26 +325,44 @@ function getStoreByName(name) { /** * Returns a [ state, setState ] pair for the selected store. Can only be called within React Components * @param {String|StoreInterface} identifier - The identifier for the wanted store + * @param {(state:any) => any} memoFn [state => state] - A memoization function to optimize component rerender. Receive the store state and return a subset of it. The component will only rerender when the subset changes. * @returns {Array} the [state, setState] pair. */ function useStore(identifier) { + var memoFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultMemoFn; var store = getStoreByIdentifier(identifier); + if (!store) { + throw 'store does not exist'; + } + + if (typeof memoFn !== 'function') { + throw 'memoFn must be a function'; + } + var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(store.state), _useState2 = _slicedToArray(_useState, 2), state = _useState2[0], set = _useState2[1]; Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function () { - if (!store.setters.includes(set)) { - store.setters.push(set); + if (!store.updatersPerMemoFunction.has(memoFn)) { + store.updatersPerMemoFunction.set(memoFn, new Set()); + } + + var updatersPerMemoFunction = store.updatersPerMemoFunction.get(memoFn); + + if (!updatersPerMemoFunction.has(set)) { + updatersPerMemoFunction.add(set); } return function () { - store.setters = store.setters.filter(function (setter) { - return setter !== set; - }); + updatersPerMemoFunction.delete(set); + + if (!updatersPerMemoFunction.size) { + store.updatersPerMemoFunction.delete(memoFn); + } }; }, []); return [state, store.setState]; diff --git a/dist/react-hookstore.js.map b/dist/react-hookstore.js.map index 1e590a8..b1d70a0 100644 --- a/dist/react-hookstore.js.map +++ b/dist/react-hookstore.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack://hookStore/webpack/universalModuleDefinition","webpack://hookStore/webpack/bootstrap","webpack://hookStore/external {\"commonjs\":\"react\",\"commonjs2\":\"react\",\"amd\":\"React\",\"root\":\"React\"}","webpack://hookStore/./src/index.js"],"names":["stores","subscriptions","defaultReducer","state","payload","StoreInterface","name","store","useReducer","dispatch","setState","getState","subscribe","bind","callback","find","c","console","warn","push","filter","getStoreByIdentifier","identifier","createStore","reducer","action","setters","forEach","setter","length","public","Object","assign","getStoreByName","e","useStore","useState","set","useEffect","includes"],"mappings":"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","file":"react-hookstore.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"react\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"React\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"hookStore\"] = factory(require(\"react\"));\n\telse\n\t\troot[\"hookStore\"] = factory(root[\"React\"]);\n})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE__0__) {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n","module.exports = __WEBPACK_EXTERNAL_MODULE__0__;","import { useState, useEffect } from 'react';\n\nlet stores = {};\nlet subscriptions = {};\n\nconst defaultReducer = (state, payload) => payload;\n\n/** The public interface of a store */\nclass StoreInterface {\n constructor(name, store, useReducer) {\n this.name = name;\n useReducer ?\n this.dispatch = store.setState : this.setState = store.setState;\n this.getState = () => store.state;\n this.subscribe = this.subscribe.bind(this);\n }\n\n /**\n * Subscribe to store changes\n * @callback callback - The function to be invoked everytime the store is updated\n * @return {Function} - Call the function returned by the method to cancel the subscription\n */\n\n /**\n *\n * @param {callback} state, action\n */\n subscribe(callback) {\n if (!callback || typeof callback !== 'function') {\n throw `store.subscribe callback argument must be a function. got '${typeof callback}' instead.`;\n }\n if (subscriptions[this.name].find(c => c === callback)) {\n console.warn('This callback is already subscribed to this store. skipping subscription');\n return;\n }\n subscriptions[this.name].push(callback);\n return () => {\n subscriptions[this.name] = subscriptions[this.name].filter(c => c !== callback);\n }\n }\n\n setState() {\n console.warn(`[React Hookstore] Store ${this.name} uses a reducer to handle its state updates. use dispatch instead of setState`)\n }\n\n dispatch() {\n console.warn(`[React Hookstore] Store ${this.name} does not use a reducer to handle state updates. use setState instead of dispatch`)\n }\n}\n\nfunction getStoreByIdentifier(identifier) {\n const name = identifier instanceof StoreInterface ? identifier.name : identifier;\n if (!stores[name]) {\n throw `Store with name ${name} does not exist`;\n }\n return stores[name];\n}\n\n/**\n * Creates a new store\n * @param {String} name - The store namespace.\n * @param {*} state [{}] - The store initial state. It can be of any type.\n * @callback reducer [null]\n * @returns {StoreInterface} The store instance.\n */\n\n /**\n *\n * @param {reducer} prevState, action - The reducer handler. Optional.\n */\nexport function createStore(name, state = {}, reducer=defaultReducer) {\n if (typeof name !== 'string') {\n throw 'Store name must be a string';\n }\n if (stores[name]) {\n throw `Store with name ${name} already exists`;\n }\n\n const store = {\n state,\n reducer,\n setState(action, callback) {\n this.state = this.reducer(this.state, action);\n this.setters.forEach(setter => setter(this.state));\n if (subscriptions[name].length) {\n subscriptions[name].forEach(c => c(this.state, action));\n }\n if (typeof callback === 'function') callback(this.state)\n },\n setters: []\n };\n store.setState = store.setState.bind(store);\n subscriptions[name] = [];\n store.public = new StoreInterface(name, store, reducer !== defaultReducer);\n\n stores = Object.assign({}, stores, { [name]: store });\n return store.public;\n}\n\n/**\n * Returns a store instance based on its name\n * @callback {String} name - The name of the wanted store\n * @returns {StoreInterface} the store instance\n */\n\nexport function getStoreByName(name) {\n try {\n return stores[name].public;\n } catch(e) {\n throw `Store with name ${name} does not exist`;\n }\n}\n\n/**\n * Returns a [ state, setState ] pair for the selected store. Can only be called within React Components\n * @param {String|StoreInterface} identifier - The identifier for the wanted store\n * @returns {Array} the [state, setState] pair.\n */\nexport function useStore(identifier) {\n const store = getStoreByIdentifier(identifier);\n\n const [ state, set ] = useState(store.state);\n\n useEffect(() => {\n if (!store.setters.includes(set)) {\n store.setters.push(set);\n }\n\n return () => {\n store.setters = store.setters.filter(setter => setter !== set)\n }\n }, [])\n\n return [ state, store.setState ];\n}\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack://hookStore/webpack/universalModuleDefinition","webpack://hookStore/webpack/bootstrap","webpack://hookStore/external {\"commonjs\":\"react\",\"commonjs2\":\"react\",\"amd\":\"React\",\"root\":\"React\"}","webpack://hookStore/./src/index.js"],"names":["stores","subscriptions","defaultReducer","state","payload","defaultMemoFn","StoreInterface","name","store","useReducer","dispatch","setState","getState","subscribe","bind","callback","find","c","console","warn","push","filter","data","getStoreByIdentifier","identifier","createStore","reducer","isPrimitiveStateWithoutReducerAndIsPreviousState","currentState","newState","updatersPerMemoFunction","forEach","updaters","memoFn","prevResult","newResult","updateComponent","length","Map","set","Set","Object","assign","public","getStoreByName","e","useStore","useState","useEffect","has","get","add","delete","size"],"mappings":"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","file":"react-hookstore.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"react\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"React\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"hookStore\"] = factory(require(\"react\"));\n\telse\n\t\troot[\"hookStore\"] = factory(root[\"React\"]);\n})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE__0__) {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n","module.exports = __WEBPACK_EXTERNAL_MODULE__0__;","import { useState, useEffect } from 'react';\n\nlet stores = {};\nlet subscriptions = {};\n\nconst defaultReducer = (state, payload) => payload;\nconst defaultMemoFn = (state) => state;\n\n/** The public interface of a store */\nclass StoreInterface {\n constructor(name, store, useReducer) {\n this.name = name;\n useReducer ?\n this.dispatch = store.setState : this.setState = store.setState;\n this.getState = () => store.state;\n this.subscribe = this.subscribe.bind(this);\n }\n\n /**\n * Subscribe to store changes\n * @param {(state:any, data:any) => void} callback - The function to be invoked everytime the store is updated\n * @return {Function} - Call the function returned by the method to cancel the subscription\n */\n subscribe(callback) {\n if (!callback || typeof callback !== 'function') {\n throw `store.subscribe callback argument must be a function. got '${typeof callback}' instead.`;\n }\n if (subscriptions[this.name].find(c => c === callback)) {\n console.warn('This callback is already subscribed to this store. skipping subscription');\n return;\n }\n subscriptions[this.name].push(callback);\n return () => {\n subscriptions[this.name] = subscriptions[this.name].filter(c => c !== callback);\n }\n }\n\n /**\n * Set the store state\n * @param {any} data - The new state value.\n */\n setState(data) {\n console.warn(`[React Hookstore] Store ${this.name} uses a reducer to handle its state updates. use dispatch instead of setState`)\n }\n /**\n * Dispatch data to the store reducer\n * @param {any} data - The data payload the reducer receives\n */\n dispatch(data) {\n console.warn(`[React Hookstore] Store ${this.name} does not use a reducer to handle state updates. use setState instead of dispatch`)\n }\n}\n\nfunction getStoreByIdentifier(identifier) {\n const name = identifier instanceof StoreInterface ? identifier.name : identifier;\n if (!stores[name]) {\n throw `Store with name ${name} does not exist`;\n }\n return stores[name];\n}\n\n/**\n * Creates a new store\n * @param {String} name - The store namespace.\n * @param {*} state [{}] - The store initial state. It can be of any type.\n * @param {(state:any, data:any) => any} reducer [null] - The reducer handler. Optional\n * @returns {StoreInterface} The store instance.\n */\nexport function createStore(name, state = {}, reducer=defaultReducer) {\n if (typeof name !== 'string') {\n throw 'Store name must be a string';\n }\n if (stores[name]) {\n throw `Store with name ${name} already exists`;\n }\n \n\n const store = {\n state,\n reducer,\n setState(data, callback) {\n const isPrimitiveStateWithoutReducerAndIsPreviousState =\n this.reducer === defaultReducer\n && data === this.state\n && typeof data !== 'object';\n\n if (isPrimitiveStateWithoutReducerAndIsPreviousState) {\n if (typeof callback === 'function') callback(this.state)\n return;\n }\n\n const currentState = this.state;\n const newState = this.reducer(this.state, data);\n this.state = newState;\n\n this.updatersPerMemoFunction.forEach((updaters, memoFn) => {\n const prevResult = memoFn(currentState);\n const newResult = memoFn(newState);\n if (prevResult === newResult) {\n return;\n }\n for (let updateComponent of updaters) {\n updateComponent(this.state);\n }\n });\n\n if (subscriptions[name].length) {\n subscriptions[name].forEach(c => c(this.state, data));\n }\n\n if (typeof callback === 'function') callback(this.state)\n },\n updatersPerMemoFunction: new Map(),\n };\n\n store.setState = store.setState.bind(store);\n store.updatersPerMemoFunction.set(defaultMemoFn, new Set())\n stores = Object.assign({}, stores, { [name]: store });\n subscriptions[name] = [];\n\n store.public = new StoreInterface(name, store, reducer !== defaultReducer);\n return store.public;\n}\n\n/**\n * Returns a store instance based on its name\n * @name {String} name - The name of the wanted store\n * @returns {StoreInterface} the store instance\n */\nexport function getStoreByName(name) {\n try {\n return stores[name].public;\n } catch(e) {\n throw `Store with name ${name} does not exist`;\n }\n}\n\n/**\n * Returns a [ state, setState ] pair for the selected store. Can only be called within React Components\n * @param {String|StoreInterface} identifier - The identifier for the wanted store\n * @param {(state:any) => any} memoFn [state => state] - A memoization function to optimize component rerender. Receive the store state and return a subset of it. The component will only rerender when the subset changes.\n * @returns {Array} the [state, setState] pair.\n */\nexport function useStore(identifier, memoFn=defaultMemoFn) {\n const store = getStoreByIdentifier(identifier);\n if (!store) {\n throw 'store does not exist';\n }\n if (typeof memoFn !== 'function') {\n throw 'memoFn must be a function';\n }\n\n const [ state, set ] = useState(store.state);\n\n useEffect(() => {\n if (!store.updatersPerMemoFunction.has(memoFn)) {\n store.updatersPerMemoFunction.set(memoFn, new Set());\n }\n \n const updatersPerMemoFunction = store.updatersPerMemoFunction.get(memoFn);\n\n if (!updatersPerMemoFunction.has(set)) {\n updatersPerMemoFunction.add(set);\n }\n\n return () => {\n updatersPerMemoFunction.delete(set);\n if (!updatersPerMemoFunction.size) {\n store.updatersPerMemoFunction.delete(memoFn);\n }\n }\n }, [])\n\n return [ state, store.setState];\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/dist/react-hookstore.min.js b/dist/react-hookstore.min.js index 574d166..e8c21e0 100644 --- a/dist/react-hookstore.min.js +++ b/dist/react-hookstore.min.js @@ -1 +1 @@ -!function(t){var e={};function s(n){if(e[n])return e[n].exports;var r=e[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,s),r.l=!0,r.exports}s.m=t,s.c=e,s.d=function(t,e,n){s.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},s.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.t=function(t,e){if(1&e&&(t=s(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(s.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)s.d(n,r,function(e){return t[e]}.bind(null,r));return n},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=1)}([function(t,e){t.exports=void 0},function(t,e,s){"use strict";s.r(e),s.d(e,"createStore",function(){return u}),s.d(e,"getStoreByName",function(){return c}),s.d(e,"useStore",function(){return f});var n=s(0);let r={},o={};const i=(t,e)=>e;class a{constructor(t,e,s){this.name=t,s?this.dispatch=e.setState:this.setState=e.setState,this.getState=(()=>e.state),this.subscribe=this.subscribe.bind(this)}subscribe(t){if(!t||"function"!=typeof t)throw`store.subscribe callback argument must be a function. got '${typeof t}' instead.`;if(!o[this.name].find(e=>e===t))return o[this.name].push(t),()=>{o[this.name]=o[this.name].filter(e=>e!==t)};console.warn("This callback is already subscribed to this store. skipping subscription")}setState(){console.warn(`[React Hookstore] Store ${this.name} uses a reducer to handle its state updates. use dispatch instead of setState`)}dispatch(){console.warn(`[React Hookstore] Store ${this.name} does not use a reducer to handle state updates. use setState instead of dispatch`)}}function u(t,e={},s=i){if("string"!=typeof t)throw"Store name must be a string";if(r[t])throw`Store with name ${t} already exists`;const n={state:e,reducer:s,setState(e,s){this.state=this.reducer(this.state,e),this.setters.forEach(t=>t(this.state)),o[t].length&&o[t].forEach(t=>t(this.state,e)),"function"==typeof s&&s(this.state)},setters:[]};return n.setState=n.setState.bind(n),o[t]=[],n.public=new a(t,n,s!==i),r=Object.assign({},r,{[t]:n}),n.public}function c(t){try{return r[t].public}catch(e){throw`Store with name ${t} does not exist`}}function f(t){const e=function(t){const e=t instanceof a?t.name:t;if(!r[e])throw`Store with name ${e} does not exist`;return r[e]}(t),[s,o]=Object(n.useState)(e.state);return Object(n.useEffect)(()=>(e.setters.includes(o)||e.setters.push(o),()=>{e.setters=e.setters.filter(t=>t!==o)}),[]),[s,e.setState]}}]); \ No newline at end of file +!function(t){var e={};function n(s){if(e[s])return e[s].exports;var r=e[s]={i:s,l:!1,exports:{}};return t[s].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=t,n.c=e,n.d=function(t,e,s){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:s})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var s=Object.create(null);if(n.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(s,r,function(e){return t[e]}.bind(null,r));return s},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([function(t,e){t.exports=void 0},function(t,e,n){"use strict";n.r(e),n.d(e,"createStore",function(){return c}),n.d(e,"getStoreByName",function(){return f}),n.d(e,"useStore",function(){return d});var s=n(0);let r={},o={};const i=(t,e)=>e,a=t=>t;class u{constructor(t,e,n){this.name=t,n?this.dispatch=e.setState:this.setState=e.setState,this.getState=(()=>e.state),this.subscribe=this.subscribe.bind(this)}subscribe(t){if(!t||"function"!=typeof t)throw`store.subscribe callback argument must be a function. got '${typeof t}' instead.`;if(!o[this.name].find(e=>e===t))return o[this.name].push(t),()=>{o[this.name]=o[this.name].filter(e=>e!==t)};console.warn("This callback is already subscribed to this store. skipping subscription")}setState(t){console.warn(`[React Hookstore] Store ${this.name} uses a reducer to handle its state updates. use dispatch instead of setState`)}dispatch(t){console.warn(`[React Hookstore] Store ${this.name} does not use a reducer to handle state updates. use setState instead of dispatch`)}}function c(t,e={},n=i){if("string"!=typeof t)throw"Store name must be a string";if(r[t])throw`Store with name ${t} already exists`;const s={state:e,reducer:n,setState(e,n){if(this.reducer===i&&e===this.state&&"object"!=typeof e)return void("function"==typeof n&&n(this.state));const s=this.state,r=this.reducer(this.state,e);this.state=r,this.updatersPerMemoFunction.forEach((t,e)=>{if(e(s)!==e(r))for(let e of t)e(this.state)}),o[t].length&&o[t].forEach(t=>t(this.state,e)),"function"==typeof n&&n(this.state)},updatersPerMemoFunction:new Map};return s.setState=s.setState.bind(s),s.updatersPerMemoFunction.set(a,new Set),r=Object.assign({},r,{[t]:s}),o[t]=[],s.public=new u(t,s,n!==i),s.public}function f(t){try{return r[t].public}catch(e){throw`Store with name ${t} does not exist`}}function d(t,e=a){const n=function(t){const e=t instanceof u?t.name:t;if(!r[e])throw`Store with name ${e} does not exist`;return r[e]}(t);if(!n)throw"store does not exist";if("function"!=typeof e)throw"memoFn must be a function";const[o,i]=Object(s.useState)(n.state);return Object(s.useEffect)(()=>{n.updatersPerMemoFunction.has(e)||n.updatersPerMemoFunction.set(e,new Set);const t=n.updatersPerMemoFunction.get(e);return t.has(i)||t.add(i),()=>{t.delete(i),t.size||n.updatersPerMemoFunction.delete(e)}},[]),[o,n.setState]}}]); \ No newline at end of file diff --git a/docs/demo.js b/docs/demo.js deleted file mode 100644 index ec4bddc..0000000 --- a/docs/demo.js +++ /dev/null @@ -1,32 +0,0 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var l=t[r]={i:r,l:!1,exports:{}};return e[r].call(l.exports,l,l.exports,n),l.l=!0,l.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var l in e)n.d(r,l,function(t){return e[t]}.bind(null,l));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t,n){"use strict";e.exports=n(3)},function(e,t,n){"use strict"; -/* -object-assign -(c) Sindre Sorhus -@license MIT -*/var r=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach(function(e){r[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,a,i=function(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),u=1;uz.length&&z.push(e)}function D(e,t,n){return null==e?0:function e(t,n,r,l){var i=typeof t;"undefined"!==i&&"boolean"!==i||(t=null);var u=!1;if(null===t)u=!0;else switch(i){case"string":case"number":u=!0;break;case"object":switch(t.$$typeof){case o:case a:u=!0}}if(u)return r(l,t,""===n?"."+U(t,0):n),1;if(u=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;cthis.eventPool.length&&this.eventPool.push(e)}function fe(e){e.eventPool=[],e.getPooled=ce,e.release=se}l(ue.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=ae)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=ae)},persist:function(){this.isPersistent=ae},isPersistent:ie,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=ie,this._dispatchInstances=this._dispatchListeners=null}}),ue.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},ue.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var o=new t;return l(o,n.prototype),n.prototype=o,n.prototype.constructor=n,n.Interface=l({},r.Interface,e),n.extend=r.extend,fe(n),n},fe(ue);var de=ue.extend({data:null}),pe=ue.extend({data:null}),me=[9,13,27,32],he=$&&"CompositionEvent"in window,ye=null;$&&"documentMode"in document&&(ye=document.documentMode);var ve=$&&"TextEvent"in window&&!ye,ge=$&&(!he||ye&&8=ye),be=String.fromCharCode(32),ke={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},we=!1;function xe(e,t){switch(e){case"keyup":return-1!==me.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function Te(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var Se=!1;var Ee={eventTypes:ke,extractEvents:function(e,t,n,r){var l=void 0,o=void 0;if(he)e:{switch(e){case"compositionstart":l=ke.compositionStart;break e;case"compositionend":l=ke.compositionEnd;break e;case"compositionupdate":l=ke.compositionUpdate;break e}l=void 0}else Se?xe(e,n)&&(l=ke.compositionEnd):"keydown"===e&&229===n.keyCode&&(l=ke.compositionStart);return l?(ge&&"ko"!==n.locale&&(Se||l!==ke.compositionStart?l===ke.compositionEnd&&Se&&(o=oe()):(re="value"in(ne=r)?ne.value:ne.textContent,Se=!0)),l=de.getPooled(l,t,n,r),o?l.data=o:null!==(o=Te(n))&&(l.data=o),H(l),o=l):o=null,(e=ve?function(e,t){switch(e){case"compositionend":return Te(t);case"keypress":return 32!==t.which?null:(we=!0,be);case"textInput":return(e=t.data)===be&&we?null:e;default:return null}}(e,n):function(e,t){if(Se)return"compositionend"===e||!he&&xe(e,t)?(e=oe(),le=re=ne=null,Se=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1