├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── index.esm.js └── index.js ├── package-lock.json ├── package.json ├── poly15.js ├── rollup.config.js └── src ├── core ├── createHook.js ├── devTools.js ├── inputsEqual.js ├── magicSelf.js ├── magicStack.js ├── useClassCallback.js ├── useClassContext.js ├── useClassDebugValue.js ├── useClassEffect.js ├── useClassEffectKey.js ├── useClassImperativeHandle.js ├── useClassMemo.js ├── useClassReducer.js ├── useClassRef.js ├── useClassRefKey.js ├── useClassState.js └── useClassStateKey.js └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": ["@salvoravida"], 6 | "rules": { 7 | "react-hooks/rules-of-hooks": 0, 8 | "no-prototype-builtins": "off", 9 | "no-self-compare": "off", 10 | "no-underscore-dangle": "off" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp 3 | *.log 4 | .vscode 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .idea/* 3 | 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@salvoravida/eslint-config/prettier.config"), 3 | "printWidth": 120, 4 | trailingComma: "none" 5 | }; 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v. 0.5.0 2 | ----------- 3 | * useClassContext - add observedBits 4 | * add useClassDebugValue ! 5 | * support React Dev Tools! 6 | 7 | v. 0.4.0 8 | ----------- 9 | * useClassState - check newState!=prevState before update 10 | * useClassEffect - fix ConcurrentMode 11 | * hooks stack counter - fix ConcurrentMode 12 | * build optimization - removed babel slicedToArray 13 | * add useClassImperativeHandle hook 14 | 15 | v. 0.3.6 16 | ----------- 17 | * add ESM build 18 | * dev update babel - rollup - eslint 19 | 20 | v. 0.3.4 21 | ----------- 22 | * upgrade to babel7 23 | 24 | v. 0.3.3 25 | ----------- 26 | * removed module on package.json 27 | 28 | v. 0.3.2 29 | ----------- 30 | * useClassContext use ReactInternals readContext 31 | 32 | v. 0.3.1 33 | ----------- 34 | * fix export useClassContext 35 | 36 | v. 0.3.0 37 | ----------- 38 | * added useClassContext 39 | * added useClassLayoutEffect alias 40 | 41 | v. 0.2.0 42 | ----------- 43 | * setState support callback => setState(value,callback); 44 | * fix circular dep. 45 | 46 | v. 0.1.9 47 | ----------- 48 | * added useClassRef (and refCallback for React15 support) 49 | * added setState() accept update func (prevState)=> nextState 50 | * internal useClassRef for optimization 51 | 52 | v. 0.1.8 53 | ----------- 54 | * inputsArrayEqual check on effect inputs 55 | * invariant for useClassEffect return 56 | * added useClassCallback.createStack 57 | 58 | v. 0.1.6 59 | ----------- 60 | * fix peerDependencies for React >=15.3.2 61 | * check typeof useEffect return is a function 62 | 63 | v. 0.1.5 64 | ----- 65 | + support react 15.3.2-15.6.2 (polyfill needed) 66 | + support useClassState initialValue creator (type function) for lazy initialization 67 | + fix effect execution order 68 | 69 | v. 0.1.4 70 | ----------- 71 | * initial support only React >=16.0.0 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Salvatore Ravidà 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-universal-hooks 3 |

UPDATE 2019.03.04 -> Recommended way to use Class Hooks

4 | https://github.com/salvoravida/react-universal-hooks 5 | 6 | # react-class-hooks [![npm version](https://img.shields.io/npm/v/react-class-hooks.svg?style=flat)](https://www.npmjs.org/package/react-class-hooks) 7 | 8 | React Hooks implementation for Class Components. Support React >= 15.3.2 9 | 10 | Installation 11 | ----------- 12 | Using [npm](https://www.npmjs.com/): 13 | 14 | $ npm install --save react-class-hooks 15 | 16 | Or [yarn](https://yarnpkg.com/): 17 | 18 | $ yarn add react-class-hooks 19 | 20 | React 15.3.2 - 15.6.2 polyfill needed 21 | ---- 22 | For React Versions 15.3.2 to 15.6.2 you have to import `react-class-hooks/poly15` in your root index.js 23 | ```javascript 24 | import React from 'react'; 25 | import ReactDOM from 'react-dom'; 26 | import 'react-class-hooks/poly15'; 27 | import App from './App'; 28 | 29 | ReactDOM.render(, document.getElementById('root')); 30 | ``` 31 | Usage 32 | ----- 33 | ```javascript 34 | import { useClassState, useClassCallback, useClassEffect } from 'react-class-hooks'; 35 | import { useClassWindowSize } from "./hooks"; 36 | 37 | class MyComponent extends React.PureComponent { 38 | constructor(props) { 39 | super(props); 40 | this.state = { /* your already existing business logic here */ }; 41 | } 42 | componentDidMount (){ /* your already existing business logic here */} 43 | componentDidUpdate (){ /* your already existing business logic here */} 44 | componentUnmount (){ /* your already existing business logic here */} 45 | 46 | render() { 47 | const { width, height } = useClassWindowSize(); 48 | 49 | return ( 50 |
51 |

windowSize : {width} x {height}

52 | {/** ..... **/} 53 |
54 | ); 55 | } 56 | } 57 | ``` 58 | 59 | hooks.js 60 | ```javascript 61 | import { useClassState, useClassEffect } from 'react-class-hooks'; 62 | 63 | export const useClassWindowSize = () => { 64 | const [size, setSize] = useClassState({ 65 | width: window.innerWidth, 66 | height: window.innerHeight, 67 | }); 68 | const handle = (() => { 69 | setSize({ 70 | width: window.innerWidth, 71 | height: window.innerHeight, 72 | }); 73 | }); 74 | useClassEffect(() => { 75 | window.addEventListener('resize', handle); 76 | return () => { 77 | window.removeEventListener('resize', handle); 78 | }; 79 | }, []); 80 | return size; 81 | }; 82 | ``` 83 | 84 | 85 | ## Abstract 86 | React Hooks implementation for Class Components. Support React >= 15.3.2 87 | 88 | ## What are React Hooks ? 89 | Official Intro -> https://reactjs.org/docs/hooks-intro.html 90 | 91 | In poor words, with use******** you are injecting @ runtime during render phase a 92 | *piece of {state, didMount, didUpdate, unMount}* just in one line of code, 93 | *without collision with eventually already existing state & lifecycle of your components.*
94 | You can think Hooks like Mixin without Mixin drawbacks, because of Runtime Injection&Isolation. 95 | 96 | ## Why Hooks? 97 | Share Business Logic (*piece of [state, didMount, didUpdate, unMount]*) between components in one line of code.
98 | Does it have a performance cost? Of course, injecting @ runtime have always a cost. So Why?
99 | Share Business Logic (*piece of [state, didMount, didUpdate, unMount]*) beetween components in one line of code. 100 | 101 | ## Why Hooks for Class Components? 102 | * Why not? (of course the useClassState used alone does not make sense in Class Comp. The real advantage is partial state isolation in Custom Hooks) 103 | * In future you could have custom Hooks for functional components, but you want to use it in a complex Class Container for any reason. 104 | * You can play with hooks today in already existing apps, testing them in class components. `react-class-hooks` should be fine with React >= 15.3.2 105 | * useClassMemo & useClassCallback make PureComponents 100% pure! (max performance!) 106 | * You could use Advanded Class Hooks concepts (see below) 107 | 108 | ## Use Case : Make PureComponent 100% Pure 109 | ```javascript 110 | import { useClassCallback } from 'react-class-hooks'; 111 | 112 | //named stack hooks -> see below (isolated named stack - no order rules drawbacks) 113 | const myComponentCallback = useClassCallback.createStack('myComponentCallback'); 114 | 115 | class MyComponent extends React.PureComponent { 116 | render (){ 117 | //.... 118 | } 119 | } 120 | 121 | class Container extends React.PureComponent { 122 | render (){ 123 | {this.props.arrayProp.map(el=> 124 | someAction(el.id) , [el.id])} /> 125 | )} 126 | } 127 | } 128 | ``` 129 | 130 | ## Does `react-class-hooks` use under the hood React official hooks? 131 | No. It implement useClass*** without React use****. It should supports React >=15.3.2
132 | 133 | ## Api Reference 134 | Api Reference are the same as official ones, so you can see api reference @ https://reactjs.org/docs/hooks-reference.html 135 |
136 | Currently supported api: 137 | 138 | * useClassState -> create & use a partial isolated Component State. 139 | * useClassEffect -> inject code on didMount+didUpdate+didUnmount 140 | * useClassMemo -> memoize values 141 | * useClassCallback -> memoize dynamic callbacks -> useful on PureComponent when rendering Arrays. 142 | * useClassReducer -> a Redux-like local state 143 | * useClassRef -> a mutable ref object with .current 144 | * useClassContext -> use Context 145 | * useClassLayoutEffect -> alias for useClassEffect 146 | * useClassImperativeHandle -> just for compatibility with useImperativeHandle. 147 | 148 | ## Advanded Class Hooks : Named Hooks 149 | Hooks are anonymous, so order matters in the "render" method, because there is no other way to differentiate from one call to another in the same render. 150 | But you could need one inside a condition, or you want to share state beetween 2 subRenders in the same render phase. 151 | 152 | let's see an example : 153 | ```javascript 154 | const useMyLoggerState = useClassState.create("MyLogger"); 155 | const useMyLoggerEffect = useClassEffect.create("MyLogger"); 156 | 157 | class App extends React.PureComponent { 158 | renderLogger() { 159 | const [logger, setLogger] = useMyLoggerState(0); 160 | return ( 161 | <> 162 |

Hidden LOGGER COUNTER {logger}

163 | 166 | 167 | ); 168 | } 169 | 170 | render() { 171 | const [count, setCount] = useClassState(0); 172 | const { width, height } = useClassWindowSize(); 173 | 174 | if (count % 5 === 0) { 175 | const [logger] = useMyLoggerState(0); 176 | useMyLoggerEffect(() => { 177 | document.title = "logged times " + logger; 178 | }); 179 | } 180 | 181 | const [count2, setCount2] = useClassState(0); 182 | 183 | return ( 184 |
185 |

186 | windowSize : {width}x{height} 187 |

188 |

Anonymous Counter 1 {count}

189 | 190 | {count % 5 === 0 && this.renderLogger()} 191 |

Other Anonymous Counter {count2}

192 | 193 |
194 | ); 195 | } 196 | } 197 | ``` 198 | https://codesandbox.io/s/v8y8o3m737 199 | 200 | I know that hooks should not be inside a condition, because it breaks the order rule, but a Named Hooks 201 | does not break anonymous hooks stack, as it has a isolated named own state. 202 | Note that named hooks share context so calling 2 times the same Named Hook in the same render phase, 203 | will get the same value! 204 | ```javascript 205 | render () { 206 | 207 | const [logger, setLogger] = useMyLoggerState({}); 208 | 209 | const [logger2, setLogger2] = useMyLoggerState({}); 210 | 211 | // => logger2===logger !! 212 | 213 | } 214 | ``` 215 | May it be useful or not? In many cases custom hooks are based on one call only of useState and useEffect! 216 | In such case using Named Hooks is better because the created *namedCustomHooks* does not need hook order rule! 217 | 218 | let's see a useClassWindowSize with named hooks: 219 | ```javascript 220 | import { useClassState, useClassEffect } from 'react-class-hooks'; 221 | 222 | const useClassWsState= useClassState.create("WindowSize"); 223 | const useClassWsEffect= useClassEffect.create("WindowSize"); 224 | 225 | const useClassWindowSize = () => { 226 | const [size, setSize] = useClassWsState({ 227 | width: window.innerWidth, 228 | height: window.innerHeight, 229 | }); 230 | const handle = (() => { 231 | setSize({ 232 | width: window.innerWidth, 233 | height: window.innerHeight, 234 | }); 235 | }); 236 | 237 | useClassWsEffect(() => { 238 | window.addEventListener('resize', handle); 239 | return () => { 240 | window.removeEventListener('resize', handle); 241 | }; 242 | }, []); 243 | return size; 244 | }; 245 | ``` 246 | Moreover debugging is easier with Named Hooks : 247 |

248 | react-adal 249 |

250 | Symbol(WindowSize) is more clear than Symbol(States-0) and if you have more custom hooks used in a component, 251 | you have to check the order in the source ... 252 | 253 | ## Advanded Class Hooks : Named Stack Hooks 254 | If you need an ordered list of Named Hooks you could find useful the last advanced api concept. 255 | ```javascript 256 | const useMyLoggerStateStack = useClassState.createStack("MyLoggerStack"); 257 | 258 | class App extends React.PureComponent { 259 | render () { 260 | 261 | const [logger, setLogger] = useMyLoggerStateStack({}); 262 | //.... 263 | const [logger2, setLogger2] = useMyLoggerStateStack({}); 264 | 265 | // => logger2 !== logger !! 266 | 267 | } 268 | } 269 | ``` 270 | 271 | ## How can i create Universal Custom Hooks in future? 272 | https://github.com/salvoravida/react-universal-hooks 273 | 274 | # Feedback 275 | 276 | Let me know what do you think about!
277 | *Enjoy it? Star this project!* :D 278 | 279 | # Todo 280 | * didCatch support 281 | * tests 282 | * others api 283 | * more custom hooks examples 284 | * better docs 285 | 286 | any idea? let me know and contribute! 287 | 288 | Contributors 289 | ------------ 290 | See [Contributors](https://github.com/salvoravida/react-class-hooks/graphs/contributors). 291 | 292 | License 293 | ------- 294 | [MIT License](https://github.com/salvoravida/react-class-hooks/blob/master/LICENSE.md). 295 | -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function _typeof(obj) { 4 | "@babel/helpers - typeof"; 5 | 6 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 7 | _typeof = function (obj) { 8 | return typeof obj; 9 | }; 10 | } else { 11 | _typeof = function (obj) { 12 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 13 | }; 14 | } 15 | 16 | return _typeof(obj); 17 | } 18 | 19 | function _defineProperty(obj, key, value) { 20 | if (key in obj) { 21 | Object.defineProperty(obj, key, { 22 | value: value, 23 | enumerable: true, 24 | configurable: true, 25 | writable: true 26 | }); 27 | } else { 28 | obj[key] = value; 29 | } 30 | 31 | return obj; 32 | } 33 | 34 | var isProduction = process.env.NODE_ENV === 'production'; 35 | var prefix = 'Invariant failed'; 36 | function invariant(condition, message) { 37 | if (condition) { 38 | return; 39 | } 40 | if (isProduction) { 41 | throw new Error(prefix); 42 | } 43 | throw new Error(prefix + ": " + (message || '')); 44 | } 45 | 46 | React.PureComponent.prototype.componentDidMount = function () {}; 47 | 48 | React.Component.prototype.componentDidMount = function () {}; 49 | 50 | invariant(typeof Symbol === 'function' && Symbol["for"], 'react-class-hooks needs Symbols!'); // Separate objects for better debugging. 51 | 52 | var MAGIC_STATES = Symbol["for"]('magicStates'); 53 | var MAGIC_EFFECTS = Symbol["for"]('magicEffects'); 54 | var MAGIC_MEMOS = Symbol["for"]('magicMemos'); 55 | var MAGIC_REFS = Symbol["for"]('magicRefs'); 56 | var MAGIC_STACKS = Symbol["for"]('magicStacks'); 57 | var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; 58 | var isReact15 = React.version.indexOf('15') === 0; // React 15.3.2 support + Polyfill 59 | 60 | var instanceKey = isReact15 ? '_instance' : 'stateNode'; 61 | 62 | if (isReact15) { 63 | invariant(ReactInternals, 'Please for React ^15.3.2 - 15.6.2 import "react-class-hooks/poly15" in your root index.js!'); 64 | } 65 | 66 | function getMagicSelf() { 67 | invariant(ReactInternals.ReactCurrentOwner.current, 'You are using Hooks outside of "render" React.Component Method!'); 68 | return ReactInternals.ReactCurrentOwner.current[instanceKey]; 69 | } 70 | var getMagicDispatcher = function getMagicDispatcher() { 71 | return ReactInternals.ReactCurrentDispatcher.current; 72 | }; 73 | function checkSymbol(name, keySymbol) { 74 | invariant(_typeof(keySymbol) === 'symbol', "".concat(name, " - Expected a Symbol for key!")); 75 | } 76 | 77 | /** 78 | * https://github.com/salvoravida/react-class-hooks 79 | */ 80 | function MagicStack(StackName) { 81 | var _this = this; 82 | 83 | this.name = StackName; 84 | this.symbol = Symbol("".concat(this.name, ".Stack")); // this.cleanSymbol = Symbol(`${this.name}.Stack.Cleaner`); 85 | 86 | this.keys = []; 87 | 88 | this.getKey = function (stackIndex) { 89 | var len = _this.keys.length; // create if not exist 90 | 91 | if (stackIndex > len) { 92 | for (var i = len; i < stackIndex; i += 1) { 93 | _this.keys.push(Symbol("".concat(_this.name, "-").concat(i))); 94 | } 95 | } 96 | 97 | return _this.keys[stackIndex - 1]; 98 | }; 99 | } 100 | function useMagicStack(magicStack, hook) { 101 | // inject next renders stack counter cleaner 102 | var self = getMagicSelf(); 103 | 104 | if (!self[MAGIC_STACKS]) { 105 | self[MAGIC_STACKS] = {}; 106 | var renderFunc = self.render.bind(self); 107 | 108 | self.render = function () { 109 | Object.getOwnPropertySymbols(self[MAGIC_STACKS]).forEach(function (k) { 110 | self[MAGIC_STACKS][k] = 0; 111 | }); 112 | return renderFunc.apply(void 0, arguments); 113 | }; 114 | } // stack counter init 115 | 116 | 117 | if (!self[MAGIC_STACKS][magicStack.symbol]) { 118 | self[MAGIC_STACKS][magicStack.symbol] = 0; 119 | } // stack counter update 120 | 121 | 122 | self[MAGIC_STACKS][magicStack.symbol] += 1; 123 | 124 | for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 125 | args[_key - 2] = arguments[_key]; 126 | } 127 | 128 | return hook.apply(void 0, [magicStack.getKey(self[MAGIC_STACKS][magicStack.symbol])].concat(args)); 129 | } 130 | 131 | function createHook(stackName, hook) { 132 | var stack = new MagicStack(stackName); 133 | return function () { 134 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 135 | args[_key] = arguments[_key]; 136 | } 137 | 138 | if (args && args.length && _typeof(args[0]) === 'symbol') return hook.apply(void 0, args); 139 | return useMagicStack.apply(void 0, [stack, hook].concat(args)); 140 | }; 141 | } 142 | function createNamedHook(name, hook) { 143 | var keySymbol = Symbol(name); 144 | return hook.bind(null, keySymbol); 145 | } 146 | 147 | var devToolConfig = { 148 | active: false, 149 | stateKey: '__UNIVERSAL_HOOKS__', 150 | show: 'object' // object, array, map 151 | 152 | }; 153 | function supportReactDevTools(_ref) { 154 | var active = _ref.active, 155 | stateKey = _ref.stateKey, 156 | show = _ref.show; 157 | if (stateKey) devToolConfig.stateKey = stateKey; 158 | if (show) devToolConfig.show = show; 159 | devToolConfig.active = !!active; 160 | } 161 | function setDevToolsHookState(name, state) { 162 | if (devToolConfig.active) { 163 | var self = getMagicSelf(); 164 | var stateKey = devToolConfig.stateKey, 165 | show = devToolConfig.show; 166 | if (!self.state) self.state = {}; 167 | if (!self.state[stateKey]) self.state[stateKey] = show === 'map' ? new Map() : show === 'array' ? [] : {}; 168 | 169 | if (show === 'map') { 170 | self.state[stateKey].set(name, state); 171 | } else if (show === 'array') { 172 | var hookState = self.state[stateKey].find(function (h) { 173 | return h.hasOwnProperty(name); 174 | }); 175 | 176 | if (hookState) { 177 | hookState[name] = state; 178 | } else { 179 | self.state[stateKey].push(_defineProperty({}, name, state)); 180 | } 181 | } else { 182 | var hookNames = Object.keys(self.state[stateKey]); 183 | var hookName = hookNames.find(function (s) { 184 | return s.split(':')[1] === name; 185 | }); 186 | self.state[stateKey][hookName || "".concat(hookNames.length.toString().padStart(2, '0'), ":").concat(name)] = state; 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * https://github.com/salvoravida/react-class-hooks 193 | */ 194 | function useClassStateKey(keySymbol, initialValue) { 195 | checkSymbol('useClassStateKey', keySymbol); 196 | var self = getMagicSelf(); // first time Render && first Hook 197 | 198 | if (!self[MAGIC_STATES]) self[MAGIC_STATES] = {}; // first time Render -> assign initial Value and create Setter 199 | 200 | if (!self[MAGIC_STATES].hasOwnProperty(keySymbol)) { 201 | self[MAGIC_STATES][keySymbol] = { 202 | value: typeof initialValue === 'function' ? initialValue() : initialValue, 203 | setValue: function setValue(value, callback) { 204 | var newState = typeof value === 'function' ? value(self[MAGIC_STATES][keySymbol].value) : value; 205 | 206 | if (self[MAGIC_STATES][keySymbol].value !== newState) { 207 | self[MAGIC_STATES][keySymbol].value = newState; 208 | 209 | if (self.updater.isMounted(self)) { 210 | self.updater.enqueueForceUpdate(self, callback); 211 | } 212 | } 213 | } 214 | }; 215 | } 216 | 217 | var _self$MAGIC_STATES$ke = self[MAGIC_STATES][keySymbol], 218 | value = _self$MAGIC_STATES$ke.value, 219 | setValue = _self$MAGIC_STATES$ke.setValue; 220 | setDevToolsHookState(keySymbol.description, value); 221 | return [value, setValue]; 222 | } 223 | 224 | /** 225 | * https://github.com/salvoravida/react-class-hooks 226 | */ 227 | var useClassState = createHook('States', useClassStateKey); 228 | 229 | useClassState.create = function (name) { 230 | return createNamedHook(name, useClassStateKey); 231 | }; 232 | 233 | useClassState.createStack = function (stackName) { 234 | return createHook(stackName, useClassStateKey); 235 | }; 236 | 237 | function inputsArrayEqual(inputs, prevInputs) { 238 | invariant(inputs.length === prevInputs.length, 'Hooks inputs array length should be constant between renders!'); // Object.is polyfill 239 | 240 | for (var i = 0; i < inputs.length; i += 1) { 241 | var val1 = inputs[i]; 242 | var val2 = prevInputs[i]; 243 | 244 | if (!(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / val2) || val1 !== val1 && val2 !== val2)) { 245 | // eslint-disable-line 246 | return false; 247 | } 248 | } 249 | 250 | return true; 251 | } 252 | 253 | /** 254 | * https://github.com/salvoravida/react-class-hooks 255 | */ 256 | var useClassEffectKey = function useClassEffectKey(keySymbol, creator, inputs) { 257 | var onlyDidUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 258 | checkSymbol('useClassEffect', keySymbol); 259 | invariant(typeof creator === 'function', 'Creator should be a function!'); 260 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 261 | var self = getMagicSelf(); // create MAGIC_EFFECTS if not exists 262 | 263 | if (!self[MAGIC_EFFECTS]) self[MAGIC_EFFECTS] = {}; // First Render -> Assign creator, inputs and inject methods 264 | // TODO didCatch 265 | 266 | if (!self[MAGIC_EFFECTS].hasOwnProperty(keySymbol)) { 267 | self[MAGIC_EFFECTS][keySymbol] = { 268 | creator: creator, 269 | inputs: inputs 270 | }; 271 | 272 | if (!onlyDidUpdate) { 273 | // inject componentDidMount 274 | var didMount = typeof self.componentDidMount === 'function' ? self.componentDidMount.bind(self) : undefined; 275 | 276 | self.componentDidMount = function () { 277 | if (didMount) didMount(); 278 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); // save last executed inputs 279 | 280 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 281 | invariant(!self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 'useClassEffect return (Effect Cleaner) should be Function or Void !'); 282 | }; 283 | } // inject componentDidUpdate 284 | 285 | 286 | var didUpdate = typeof self.componentDidUpdate === 'function' ? self.componentDidUpdate.bind(self) : undefined; 287 | 288 | self.componentDidUpdate = function () { 289 | if (didUpdate) didUpdate.apply(void 0, arguments); // execute if no inputs || inputs array has values and values changed 290 | 291 | var execute = !self[MAGIC_EFFECTS][keySymbol].inputs || !inputsArrayEqual(self[MAGIC_EFFECTS][keySymbol].inputs, self[MAGIC_EFFECTS][keySymbol].prevInputs); 292 | 293 | if (execute) { 294 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 295 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); // save last executed inputs! 296 | 297 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 298 | invariant(!self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 'useClassEffect return (Effect Cleaner) should be Function or Void !'); 299 | } 300 | }; // inject componentWillUnmount 301 | 302 | 303 | var unmount = typeof self.componentWillUnmount === 'function' ? self.componentWillUnmount.bind(self) : undefined; 304 | 305 | self.componentWillUnmount = function () { 306 | if (unmount) unmount(); 307 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 308 | }; 309 | } else { 310 | // next renders 311 | self[MAGIC_EFFECTS][keySymbol].creator = creator; 312 | self[MAGIC_EFFECTS][keySymbol].inputs = inputs; 313 | } 314 | }; 315 | 316 | var useClassEffect = createHook('Effects', useClassEffectKey); 317 | 318 | useClassEffect.create = function (name) { 319 | return createNamedHook(name, useClassEffectKey); 320 | }; 321 | 322 | useClassEffect.createStack = function (stackName) { 323 | return createHook(stackName, useClassEffectKey); 324 | }; 325 | 326 | /** 327 | * https://github.com/salvoravida/react-class-hooks 328 | */ 329 | var useClassMemoKey = function useClassMemoKey(keySymbol, creator, inputs) { 330 | checkSymbol('useClassMemo', keySymbol); 331 | invariant(typeof creator === 'function', 'Creator should be a function!'); 332 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 333 | var self = getMagicSelf(); // create magic Memos if not exists 334 | 335 | if (!self[MAGIC_MEMOS]) self[MAGIC_MEMOS] = {}; // First Render -> assign creator, inputs, value 336 | 337 | if (!self[MAGIC_MEMOS].hasOwnProperty(keySymbol)) { 338 | self[MAGIC_MEMOS][keySymbol] = { 339 | creator: creator, 340 | inputs: inputs, 341 | value: creator() 342 | }; 343 | } else { 344 | // next renders 345 | var execute = false; 346 | 347 | if (!inputs) { 348 | if (creator !== self[MAGIC_MEMOS][keySymbol].creator) { 349 | execute = true; 350 | } 351 | } else { 352 | execute = !inputsArrayEqual(inputs, self[MAGIC_MEMOS][keySymbol].inputs); 353 | } 354 | 355 | if (execute) { 356 | self[MAGIC_MEMOS][keySymbol] = { 357 | creator: creator, 358 | inputs: inputs, 359 | value: creator() 360 | }; 361 | } 362 | } 363 | 364 | var returnValue = self[MAGIC_MEMOS][keySymbol].value; 365 | setDevToolsHookState(keySymbol.description, returnValue); 366 | return returnValue; 367 | }; 368 | var useClassMemo = createHook('Memos', useClassMemoKey); 369 | 370 | useClassMemo.create = function (name) { 371 | return createNamedHook(name, useClassMemoKey); 372 | }; 373 | 374 | useClassMemo.createStack = function (stackName) { 375 | return createHook(stackName, useClassMemoKey); 376 | }; 377 | 378 | /** 379 | * https://github.com/salvoravida/react-class-hooks 380 | */ 381 | function useClassCallbackKey(keySymbol, callback, inputs) { 382 | return useClassMemoKey(keySymbol, function () { 383 | return callback; 384 | }, inputs); 385 | } 386 | var useClassCallback = createHook('Callbacks', useClassCallbackKey); 387 | 388 | useClassCallback.create = function (name) { 389 | return createNamedHook(name, useClassCallbackKey); 390 | }; 391 | 392 | useClassCallback.createStack = function (stackName) { 393 | return createHook(stackName, useClassCallbackKey); 394 | }; 395 | 396 | /** 397 | * https://github.com/salvoravida/react-class-hooks 398 | */ 399 | var useClassReducerKey = function useClassReducerKey(keySymbol, reducer, initialState) { 400 | var stateSetState = useClassStateKey(keySymbol, initialState); 401 | var state = stateSetState[0]; 402 | var setState = stateSetState[1]; 403 | 404 | function dispatch(action) { 405 | var nextState = reducer(state, action); 406 | setState(nextState); 407 | } 408 | 409 | return [state, dispatch]; 410 | }; 411 | var useClassReducer = createHook('Reducers', useClassReducerKey); 412 | 413 | useClassReducer.create = function (name) { 414 | return createNamedHook(name, useClassReducerKey); 415 | }; 416 | 417 | /** 418 | * https://github.com/salvoravida/react-class-hooks 419 | */ 420 | function useClassRefKey(keySymbol, initialValue) { 421 | checkSymbol('useClassRefKey', keySymbol); 422 | var self = getMagicSelf(); // first time Render && first Hook 423 | 424 | if (!self[MAGIC_REFS]) self[MAGIC_REFS] = {}; // first time Render -> assign initial Value 425 | 426 | if (!self[MAGIC_REFS].hasOwnProperty(keySymbol)) { 427 | var ref = { 428 | current: initialValue 429 | }; 430 | Object.seal(ref); 431 | self[MAGIC_REFS][keySymbol] = ref; 432 | } 433 | 434 | var returnValue = self[MAGIC_REFS][keySymbol]; 435 | setDevToolsHookState(keySymbol.description, returnValue); 436 | return returnValue; 437 | } 438 | 439 | /** 440 | * https://github.com/salvoravida/react-class-hooks 441 | */ 442 | var useClassRef = createHook('Refs', useClassRefKey); 443 | 444 | useClassRef.create = function (name) { 445 | return createNamedHook(name, useClassRefKey); 446 | }; 447 | 448 | useClassRef.createStack = function (stackName) { 449 | return createHook(stackName, useClassRefKey); 450 | }; // poly 15 ref 451 | 452 | 453 | var refCallback = function refCallback(refObject) { 454 | return function (ref) { 455 | refObject.current = ref; 456 | }; 457 | }; 458 | 459 | /** 460 | * https://github.com/salvoravida/react-class-hooks 461 | */ 462 | function useClassContextKey(keySymbol, context, observedBits) { 463 | checkSymbol('useClassContext', keySymbol); 464 | getMagicSelf(); // invariant hook outside render method 465 | 466 | invariant(context && context.Provider && context.Consumer, 'Context should be React.createContext object!'); 467 | var contextValue = getMagicDispatcher().readContext(context, observedBits); 468 | setDevToolsHookState(keySymbol.description, contextValue); 469 | return contextValue; 470 | } 471 | var useClassContext = createHook('Contexts', useClassContextKey); 472 | 473 | function useClassImperativeHandle(ref, create, deps) { 474 | invariant(typeof create === 'function', "Expected useImperativeHandle() second argument to be a function that creates a handle. Instead received: ".concat(create !== null ? _typeof(create) : 'null')); 475 | invariant(deps === null || deps === undefined || Array.isArray(deps), 'Hook received a final argument that is not an array!'); 476 | var effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; // eslint-disable-next-line consistent-return 477 | 478 | useClassEffect(function () { 479 | if (typeof ref === 'function') { 480 | var refCallback = ref; 481 | refCallback(create()); // eslint-disable-next-line func-names 482 | 483 | return function () { 484 | refCallback(null); 485 | }; 486 | } 487 | 488 | if (ref !== null && ref !== undefined) { 489 | var refObject = ref; 490 | invariant(refObject.hasOwnProperty('current'), "Expected useImperativeHandle() first argument to either be a ref callback or React.createRef() object. Instead received: an object with keys {".concat(Object.keys(refObject).join(', '), "}")); 491 | refObject.current = create(); // eslint-disable-next-line func-names 492 | 493 | return function () { 494 | refObject.current = null; 495 | }; 496 | } 497 | }, effectDeps); 498 | } 499 | 500 | /** 501 | * https://github.com/salvoravida/react-class-hooks 502 | */ 503 | function useClassDebugValueKey(keySymbol, value, formatter) { 504 | checkSymbol('useDebugValueKey', keySymbol); 505 | var viewValue = typeof formatter === 'function' ? formatter(value) : value; 506 | setDevToolsHookState(keySymbol.description, viewValue); 507 | } 508 | var useClassDebugValue = createHook('DebugValue', useClassDebugValueKey); 509 | 510 | /** 511 | * https://github.com/salvoravida/react-class-hooks 512 | */ 513 | var useClassLayoutEffect = useClassEffect; 514 | 515 | export { refCallback, supportReactDevTools, useClassCallback, useClassContext, useClassDebugValue, useClassEffect, useClassImperativeHandle, useClassLayoutEffect, useClassMemo, useClassReducer, useClassRef, useClassState }; 516 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var React = require('react'); 6 | 7 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 8 | 9 | var React__default = /*#__PURE__*/_interopDefaultLegacy(React); 10 | 11 | function _typeof(obj) { 12 | "@babel/helpers - typeof"; 13 | 14 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 15 | _typeof = function (obj) { 16 | return typeof obj; 17 | }; 18 | } else { 19 | _typeof = function (obj) { 20 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 21 | }; 22 | } 23 | 24 | return _typeof(obj); 25 | } 26 | 27 | function _defineProperty(obj, key, value) { 28 | if (key in obj) { 29 | Object.defineProperty(obj, key, { 30 | value: value, 31 | enumerable: true, 32 | configurable: true, 33 | writable: true 34 | }); 35 | } else { 36 | obj[key] = value; 37 | } 38 | 39 | return obj; 40 | } 41 | 42 | var isProduction = process.env.NODE_ENV === 'production'; 43 | var prefix = 'Invariant failed'; 44 | function invariant(condition, message) { 45 | if (condition) { 46 | return; 47 | } 48 | if (isProduction) { 49 | throw new Error(prefix); 50 | } 51 | throw new Error(prefix + ": " + (message || '')); 52 | } 53 | 54 | React__default['default'].PureComponent.prototype.componentDidMount = function () {}; 55 | 56 | React__default['default'].Component.prototype.componentDidMount = function () {}; 57 | 58 | invariant(typeof Symbol === 'function' && Symbol["for"], 'react-class-hooks needs Symbols!'); // Separate objects for better debugging. 59 | 60 | var MAGIC_STATES = Symbol["for"]('magicStates'); 61 | var MAGIC_EFFECTS = Symbol["for"]('magicEffects'); 62 | var MAGIC_MEMOS = Symbol["for"]('magicMemos'); 63 | var MAGIC_REFS = Symbol["for"]('magicRefs'); 64 | var MAGIC_STACKS = Symbol["for"]('magicStacks'); 65 | var ReactInternals = React__default['default'].__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; 66 | var isReact15 = React__default['default'].version.indexOf('15') === 0; // React 15.3.2 support + Polyfill 67 | 68 | var instanceKey = isReact15 ? '_instance' : 'stateNode'; 69 | 70 | if (isReact15) { 71 | invariant(ReactInternals, 'Please for React ^15.3.2 - 15.6.2 import "react-class-hooks/poly15" in your root index.js!'); 72 | } 73 | 74 | function getMagicSelf() { 75 | invariant(ReactInternals.ReactCurrentOwner.current, 'You are using Hooks outside of "render" React.Component Method!'); 76 | return ReactInternals.ReactCurrentOwner.current[instanceKey]; 77 | } 78 | var getMagicDispatcher = function getMagicDispatcher() { 79 | return ReactInternals.ReactCurrentDispatcher.current; 80 | }; 81 | function checkSymbol(name, keySymbol) { 82 | invariant(_typeof(keySymbol) === 'symbol', "".concat(name, " - Expected a Symbol for key!")); 83 | } 84 | 85 | /** 86 | * https://github.com/salvoravida/react-class-hooks 87 | */ 88 | function MagicStack(StackName) { 89 | var _this = this; 90 | 91 | this.name = StackName; 92 | this.symbol = Symbol("".concat(this.name, ".Stack")); // this.cleanSymbol = Symbol(`${this.name}.Stack.Cleaner`); 93 | 94 | this.keys = []; 95 | 96 | this.getKey = function (stackIndex) { 97 | var len = _this.keys.length; // create if not exist 98 | 99 | if (stackIndex > len) { 100 | for (var i = len; i < stackIndex; i += 1) { 101 | _this.keys.push(Symbol("".concat(_this.name, "-").concat(i))); 102 | } 103 | } 104 | 105 | return _this.keys[stackIndex - 1]; 106 | }; 107 | } 108 | function useMagicStack(magicStack, hook) { 109 | // inject next renders stack counter cleaner 110 | var self = getMagicSelf(); 111 | 112 | if (!self[MAGIC_STACKS]) { 113 | self[MAGIC_STACKS] = {}; 114 | var renderFunc = self.render.bind(self); 115 | 116 | self.render = function () { 117 | Object.getOwnPropertySymbols(self[MAGIC_STACKS]).forEach(function (k) { 118 | self[MAGIC_STACKS][k] = 0; 119 | }); 120 | return renderFunc.apply(void 0, arguments); 121 | }; 122 | } // stack counter init 123 | 124 | 125 | if (!self[MAGIC_STACKS][magicStack.symbol]) { 126 | self[MAGIC_STACKS][magicStack.symbol] = 0; 127 | } // stack counter update 128 | 129 | 130 | self[MAGIC_STACKS][magicStack.symbol] += 1; 131 | 132 | for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 133 | args[_key - 2] = arguments[_key]; 134 | } 135 | 136 | return hook.apply(void 0, [magicStack.getKey(self[MAGIC_STACKS][magicStack.symbol])].concat(args)); 137 | } 138 | 139 | function createHook(stackName, hook) { 140 | var stack = new MagicStack(stackName); 141 | return function () { 142 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 143 | args[_key] = arguments[_key]; 144 | } 145 | 146 | if (args && args.length && _typeof(args[0]) === 'symbol') return hook.apply(void 0, args); 147 | return useMagicStack.apply(void 0, [stack, hook].concat(args)); 148 | }; 149 | } 150 | function createNamedHook(name, hook) { 151 | var keySymbol = Symbol(name); 152 | return hook.bind(null, keySymbol); 153 | } 154 | 155 | var devToolConfig = { 156 | active: false, 157 | stateKey: '__UNIVERSAL_HOOKS__', 158 | show: 'object' // object, array, map 159 | 160 | }; 161 | function supportReactDevTools(_ref) { 162 | var active = _ref.active, 163 | stateKey = _ref.stateKey, 164 | show = _ref.show; 165 | if (stateKey) devToolConfig.stateKey = stateKey; 166 | if (show) devToolConfig.show = show; 167 | devToolConfig.active = !!active; 168 | } 169 | function setDevToolsHookState(name, state) { 170 | if (devToolConfig.active) { 171 | var self = getMagicSelf(); 172 | var stateKey = devToolConfig.stateKey, 173 | show = devToolConfig.show; 174 | if (!self.state) self.state = {}; 175 | if (!self.state[stateKey]) self.state[stateKey] = show === 'map' ? new Map() : show === 'array' ? [] : {}; 176 | 177 | if (show === 'map') { 178 | self.state[stateKey].set(name, state); 179 | } else if (show === 'array') { 180 | var hookState = self.state[stateKey].find(function (h) { 181 | return h.hasOwnProperty(name); 182 | }); 183 | 184 | if (hookState) { 185 | hookState[name] = state; 186 | } else { 187 | self.state[stateKey].push(_defineProperty({}, name, state)); 188 | } 189 | } else { 190 | var hookNames = Object.keys(self.state[stateKey]); 191 | var hookName = hookNames.find(function (s) { 192 | return s.split(':')[1] === name; 193 | }); 194 | self.state[stateKey][hookName || "".concat(hookNames.length.toString().padStart(2, '0'), ":").concat(name)] = state; 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * https://github.com/salvoravida/react-class-hooks 201 | */ 202 | function useClassStateKey(keySymbol, initialValue) { 203 | checkSymbol('useClassStateKey', keySymbol); 204 | var self = getMagicSelf(); // first time Render && first Hook 205 | 206 | if (!self[MAGIC_STATES]) self[MAGIC_STATES] = {}; // first time Render -> assign initial Value and create Setter 207 | 208 | if (!self[MAGIC_STATES].hasOwnProperty(keySymbol)) { 209 | self[MAGIC_STATES][keySymbol] = { 210 | value: typeof initialValue === 'function' ? initialValue() : initialValue, 211 | setValue: function setValue(value, callback) { 212 | var newState = typeof value === 'function' ? value(self[MAGIC_STATES][keySymbol].value) : value; 213 | 214 | if (self[MAGIC_STATES][keySymbol].value !== newState) { 215 | self[MAGIC_STATES][keySymbol].value = newState; 216 | 217 | if (self.updater.isMounted(self)) { 218 | self.updater.enqueueForceUpdate(self, callback); 219 | } 220 | } 221 | } 222 | }; 223 | } 224 | 225 | var _self$MAGIC_STATES$ke = self[MAGIC_STATES][keySymbol], 226 | value = _self$MAGIC_STATES$ke.value, 227 | setValue = _self$MAGIC_STATES$ke.setValue; 228 | setDevToolsHookState(keySymbol.description, value); 229 | return [value, setValue]; 230 | } 231 | 232 | /** 233 | * https://github.com/salvoravida/react-class-hooks 234 | */ 235 | var useClassState = createHook('States', useClassStateKey); 236 | 237 | useClassState.create = function (name) { 238 | return createNamedHook(name, useClassStateKey); 239 | }; 240 | 241 | useClassState.createStack = function (stackName) { 242 | return createHook(stackName, useClassStateKey); 243 | }; 244 | 245 | function inputsArrayEqual(inputs, prevInputs) { 246 | invariant(inputs.length === prevInputs.length, 'Hooks inputs array length should be constant between renders!'); // Object.is polyfill 247 | 248 | for (var i = 0; i < inputs.length; i += 1) { 249 | var val1 = inputs[i]; 250 | var val2 = prevInputs[i]; 251 | 252 | if (!(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / val2) || val1 !== val1 && val2 !== val2)) { 253 | // eslint-disable-line 254 | return false; 255 | } 256 | } 257 | 258 | return true; 259 | } 260 | 261 | /** 262 | * https://github.com/salvoravida/react-class-hooks 263 | */ 264 | var useClassEffectKey = function useClassEffectKey(keySymbol, creator, inputs) { 265 | var onlyDidUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 266 | checkSymbol('useClassEffect', keySymbol); 267 | invariant(typeof creator === 'function', 'Creator should be a function!'); 268 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 269 | var self = getMagicSelf(); // create MAGIC_EFFECTS if not exists 270 | 271 | if (!self[MAGIC_EFFECTS]) self[MAGIC_EFFECTS] = {}; // First Render -> Assign creator, inputs and inject methods 272 | // TODO didCatch 273 | 274 | if (!self[MAGIC_EFFECTS].hasOwnProperty(keySymbol)) { 275 | self[MAGIC_EFFECTS][keySymbol] = { 276 | creator: creator, 277 | inputs: inputs 278 | }; 279 | 280 | if (!onlyDidUpdate) { 281 | // inject componentDidMount 282 | var didMount = typeof self.componentDidMount === 'function' ? self.componentDidMount.bind(self) : undefined; 283 | 284 | self.componentDidMount = function () { 285 | if (didMount) didMount(); 286 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); // save last executed inputs 287 | 288 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 289 | invariant(!self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 'useClassEffect return (Effect Cleaner) should be Function or Void !'); 290 | }; 291 | } // inject componentDidUpdate 292 | 293 | 294 | var didUpdate = typeof self.componentDidUpdate === 'function' ? self.componentDidUpdate.bind(self) : undefined; 295 | 296 | self.componentDidUpdate = function () { 297 | if (didUpdate) didUpdate.apply(void 0, arguments); // execute if no inputs || inputs array has values and values changed 298 | 299 | var execute = !self[MAGIC_EFFECTS][keySymbol].inputs || !inputsArrayEqual(self[MAGIC_EFFECTS][keySymbol].inputs, self[MAGIC_EFFECTS][keySymbol].prevInputs); 300 | 301 | if (execute) { 302 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 303 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); // save last executed inputs! 304 | 305 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 306 | invariant(!self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 'useClassEffect return (Effect Cleaner) should be Function or Void !'); 307 | } 308 | }; // inject componentWillUnmount 309 | 310 | 311 | var unmount = typeof self.componentWillUnmount === 'function' ? self.componentWillUnmount.bind(self) : undefined; 312 | 313 | self.componentWillUnmount = function () { 314 | if (unmount) unmount(); 315 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 316 | }; 317 | } else { 318 | // next renders 319 | self[MAGIC_EFFECTS][keySymbol].creator = creator; 320 | self[MAGIC_EFFECTS][keySymbol].inputs = inputs; 321 | } 322 | }; 323 | 324 | var useClassEffect = createHook('Effects', useClassEffectKey); 325 | 326 | useClassEffect.create = function (name) { 327 | return createNamedHook(name, useClassEffectKey); 328 | }; 329 | 330 | useClassEffect.createStack = function (stackName) { 331 | return createHook(stackName, useClassEffectKey); 332 | }; 333 | 334 | /** 335 | * https://github.com/salvoravida/react-class-hooks 336 | */ 337 | var useClassMemoKey = function useClassMemoKey(keySymbol, creator, inputs) { 338 | checkSymbol('useClassMemo', keySymbol); 339 | invariant(typeof creator === 'function', 'Creator should be a function!'); 340 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 341 | var self = getMagicSelf(); // create magic Memos if not exists 342 | 343 | if (!self[MAGIC_MEMOS]) self[MAGIC_MEMOS] = {}; // First Render -> assign creator, inputs, value 344 | 345 | if (!self[MAGIC_MEMOS].hasOwnProperty(keySymbol)) { 346 | self[MAGIC_MEMOS][keySymbol] = { 347 | creator: creator, 348 | inputs: inputs, 349 | value: creator() 350 | }; 351 | } else { 352 | // next renders 353 | var execute = false; 354 | 355 | if (!inputs) { 356 | if (creator !== self[MAGIC_MEMOS][keySymbol].creator) { 357 | execute = true; 358 | } 359 | } else { 360 | execute = !inputsArrayEqual(inputs, self[MAGIC_MEMOS][keySymbol].inputs); 361 | } 362 | 363 | if (execute) { 364 | self[MAGIC_MEMOS][keySymbol] = { 365 | creator: creator, 366 | inputs: inputs, 367 | value: creator() 368 | }; 369 | } 370 | } 371 | 372 | var returnValue = self[MAGIC_MEMOS][keySymbol].value; 373 | setDevToolsHookState(keySymbol.description, returnValue); 374 | return returnValue; 375 | }; 376 | var useClassMemo = createHook('Memos', useClassMemoKey); 377 | 378 | useClassMemo.create = function (name) { 379 | return createNamedHook(name, useClassMemoKey); 380 | }; 381 | 382 | useClassMemo.createStack = function (stackName) { 383 | return createHook(stackName, useClassMemoKey); 384 | }; 385 | 386 | /** 387 | * https://github.com/salvoravida/react-class-hooks 388 | */ 389 | function useClassCallbackKey(keySymbol, callback, inputs) { 390 | return useClassMemoKey(keySymbol, function () { 391 | return callback; 392 | }, inputs); 393 | } 394 | var useClassCallback = createHook('Callbacks', useClassCallbackKey); 395 | 396 | useClassCallback.create = function (name) { 397 | return createNamedHook(name, useClassCallbackKey); 398 | }; 399 | 400 | useClassCallback.createStack = function (stackName) { 401 | return createHook(stackName, useClassCallbackKey); 402 | }; 403 | 404 | /** 405 | * https://github.com/salvoravida/react-class-hooks 406 | */ 407 | var useClassReducerKey = function useClassReducerKey(keySymbol, reducer, initialState) { 408 | var stateSetState = useClassStateKey(keySymbol, initialState); 409 | var state = stateSetState[0]; 410 | var setState = stateSetState[1]; 411 | 412 | function dispatch(action) { 413 | var nextState = reducer(state, action); 414 | setState(nextState); 415 | } 416 | 417 | return [state, dispatch]; 418 | }; 419 | var useClassReducer = createHook('Reducers', useClassReducerKey); 420 | 421 | useClassReducer.create = function (name) { 422 | return createNamedHook(name, useClassReducerKey); 423 | }; 424 | 425 | /** 426 | * https://github.com/salvoravida/react-class-hooks 427 | */ 428 | function useClassRefKey(keySymbol, initialValue) { 429 | checkSymbol('useClassRefKey', keySymbol); 430 | var self = getMagicSelf(); // first time Render && first Hook 431 | 432 | if (!self[MAGIC_REFS]) self[MAGIC_REFS] = {}; // first time Render -> assign initial Value 433 | 434 | if (!self[MAGIC_REFS].hasOwnProperty(keySymbol)) { 435 | var ref = { 436 | current: initialValue 437 | }; 438 | Object.seal(ref); 439 | self[MAGIC_REFS][keySymbol] = ref; 440 | } 441 | 442 | var returnValue = self[MAGIC_REFS][keySymbol]; 443 | setDevToolsHookState(keySymbol.description, returnValue); 444 | return returnValue; 445 | } 446 | 447 | /** 448 | * https://github.com/salvoravida/react-class-hooks 449 | */ 450 | var useClassRef = createHook('Refs', useClassRefKey); 451 | 452 | useClassRef.create = function (name) { 453 | return createNamedHook(name, useClassRefKey); 454 | }; 455 | 456 | useClassRef.createStack = function (stackName) { 457 | return createHook(stackName, useClassRefKey); 458 | }; // poly 15 ref 459 | 460 | 461 | var refCallback = function refCallback(refObject) { 462 | return function (ref) { 463 | refObject.current = ref; 464 | }; 465 | }; 466 | 467 | /** 468 | * https://github.com/salvoravida/react-class-hooks 469 | */ 470 | function useClassContextKey(keySymbol, context, observedBits) { 471 | checkSymbol('useClassContext', keySymbol); 472 | getMagicSelf(); // invariant hook outside render method 473 | 474 | invariant(context && context.Provider && context.Consumer, 'Context should be React.createContext object!'); 475 | var contextValue = getMagicDispatcher().readContext(context, observedBits); 476 | setDevToolsHookState(keySymbol.description, contextValue); 477 | return contextValue; 478 | } 479 | var useClassContext = createHook('Contexts', useClassContextKey); 480 | 481 | function useClassImperativeHandle(ref, create, deps) { 482 | invariant(typeof create === 'function', "Expected useImperativeHandle() second argument to be a function that creates a handle. Instead received: ".concat(create !== null ? _typeof(create) : 'null')); 483 | invariant(deps === null || deps === undefined || Array.isArray(deps), 'Hook received a final argument that is not an array!'); 484 | var effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; // eslint-disable-next-line consistent-return 485 | 486 | useClassEffect(function () { 487 | if (typeof ref === 'function') { 488 | var refCallback = ref; 489 | refCallback(create()); // eslint-disable-next-line func-names 490 | 491 | return function () { 492 | refCallback(null); 493 | }; 494 | } 495 | 496 | if (ref !== null && ref !== undefined) { 497 | var refObject = ref; 498 | invariant(refObject.hasOwnProperty('current'), "Expected useImperativeHandle() first argument to either be a ref callback or React.createRef() object. Instead received: an object with keys {".concat(Object.keys(refObject).join(', '), "}")); 499 | refObject.current = create(); // eslint-disable-next-line func-names 500 | 501 | return function () { 502 | refObject.current = null; 503 | }; 504 | } 505 | }, effectDeps); 506 | } 507 | 508 | /** 509 | * https://github.com/salvoravida/react-class-hooks 510 | */ 511 | function useClassDebugValueKey(keySymbol, value, formatter) { 512 | checkSymbol('useDebugValueKey', keySymbol); 513 | var viewValue = typeof formatter === 'function' ? formatter(value) : value; 514 | setDevToolsHookState(keySymbol.description, viewValue); 515 | } 516 | var useClassDebugValue = createHook('DebugValue', useClassDebugValueKey); 517 | 518 | /** 519 | * https://github.com/salvoravida/react-class-hooks 520 | */ 521 | var useClassLayoutEffect = useClassEffect; 522 | 523 | exports.refCallback = refCallback; 524 | exports.supportReactDevTools = supportReactDevTools; 525 | exports.useClassCallback = useClassCallback; 526 | exports.useClassContext = useClassContext; 527 | exports.useClassDebugValue = useClassDebugValue; 528 | exports.useClassEffect = useClassEffect; 529 | exports.useClassImperativeHandle = useClassImperativeHandle; 530 | exports.useClassLayoutEffect = useClassLayoutEffect; 531 | exports.useClassMemo = useClassMemo; 532 | exports.useClassReducer = useClassReducer; 533 | exports.useClassRef = useClassRef; 534 | exports.useClassState = useClassState; 535 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-class-hooks", 3 | "version": "0.5.1", 4 | "description": "react-class-hooks", 5 | "main": "dist/index.js", 6 | "module": "dist/index.esm.js", 7 | "engines": { 8 | "node": ">=8", 9 | "npm": ">=5" 10 | }, 11 | "scripts": { 12 | "build": "rollup -c", 13 | "start": "rollup -c -w", 14 | "prepare": "npm run build", 15 | "prepublishOnly": "npm run build" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/salvoravida/react-class-hooks" 20 | }, 21 | "keywords": [ 22 | "react-class-hooks", 23 | "react", 24 | "class", 25 | "classes", 26 | "hooks", 27 | "react-use", 28 | "react-use-class" 29 | ], 30 | "author": "Salvatore Ravidà", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/salvoravida/react-class-hooks/issues" 34 | }, 35 | "homepage": "https://github.com/salvoravida/react-class-hooks#readme", 36 | "dependencies": {}, 37 | "peerDependencies": { 38 | "react": ">=15.3.2" 39 | }, 40 | "devDependencies": { 41 | "@babel/cli": "7.12.8", 42 | "@babel/core": "7.12.9", 43 | "@babel/preset-env": "7.12.7", 44 | "@babel/preset-react": "7.12.7", 45 | "@salvoravida/eslint-config": "0.0.4", 46 | "react": "17.0.1", 47 | "rollup": "2.34.0", 48 | "rollup-plugin-babel": "4.4.0", 49 | "rollup-plugin-node-resolve": "5.2.0", 50 | "tiny-invariant": "1.1.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /poly15.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | if (React.version.indexOf('15') === 0 && !React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) { 4 | React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { 5 | ReactCurrentOwner: require('react/lib/ReactCurrentOwner'), 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | 4 | export default [ 5 | { 6 | external: ['react'], 7 | input: 'src/index.js', 8 | output: [ 9 | { 10 | file: 'dist/index.js', 11 | format: 'cjs', 12 | sourcemap: false, 13 | globals: { 14 | react: 'React' 15 | } 16 | }, 17 | { 18 | file: 'dist/index.esm.js', 19 | format: 'esm', 20 | sourcemap: false, 21 | globals: { 22 | react: 'React' 23 | } 24 | } 25 | ], 26 | plugins: [ 27 | resolve(), 28 | babel({ 29 | exclude: 'node_modules/**/*', 30 | presets: [ 31 | [ 32 | '@babel/preset-env', 33 | { 34 | modules: false 35 | } 36 | ], 37 | '@babel/preset-react' 38 | ] 39 | }) 40 | ] 41 | } 42 | ]; 43 | -------------------------------------------------------------------------------- /src/core/createHook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { MagicStack, useMagicStack } from './magicStack'; 6 | 7 | export function createHook(stackName, hook) { 8 | const stack = new MagicStack(stackName); 9 | return (...args) => { 10 | if (args && args.length && typeof args[0] === 'symbol') return hook(...args); 11 | return useMagicStack(stack, hook, ...args); 12 | }; 13 | } 14 | 15 | export function createNamedHook(name, hook) { 16 | const keySymbol = Symbol(name); 17 | return hook.bind(null, keySymbol); 18 | } 19 | -------------------------------------------------------------------------------- /src/core/devTools.js: -------------------------------------------------------------------------------- 1 | import { getMagicSelf } from './magicSelf'; 2 | 3 | export const devToolConfig = { 4 | active: false, 5 | stateKey: '__UNIVERSAL_HOOKS__', 6 | show: 'object' // object, array, map 7 | }; 8 | 9 | export function supportReactDevTools({ active, stateKey, show }) { 10 | if (stateKey) devToolConfig.stateKey = stateKey; 11 | if (show) devToolConfig.show = show; 12 | devToolConfig.active = !!active; 13 | } 14 | 15 | export function setDevToolsHookState(name, state) { 16 | if (devToolConfig.active) { 17 | const self = getMagicSelf(); 18 | const { stateKey, show } = devToolConfig; 19 | if (!self.state) self.state = {}; 20 | if (!self.state[stateKey]) self.state[stateKey] = show === 'map' ? new Map() : show === 'array' ? [] : {}; 21 | 22 | if (show === 'map') { 23 | self.state[stateKey].set(name, state); 24 | } else if (show === 'array') { 25 | const hookState = self.state[stateKey].find(h => h.hasOwnProperty(name)); 26 | if (hookState) { 27 | hookState[name] = state; 28 | } else { 29 | self.state[stateKey].push({ [name]: state }); 30 | } 31 | } else { 32 | const hookNames = Object.keys(self.state[stateKey]); 33 | const hookName = hookNames.find(s => s.split(':')[1] === name); 34 | self.state[stateKey][hookName || `${hookNames.length.toString().padStart(2, '0')}:${name}`] = state; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/core/inputsEqual.js: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | 3 | export function inputsArrayEqual(inputs, prevInputs) { 4 | invariant(inputs.length === prevInputs.length, 'Hooks inputs array length should be constant between renders!'); 5 | 6 | // Object.is polyfill 7 | for (let i = 0; i < inputs.length; i += 1) { 8 | const val1 = inputs[i]; 9 | const val2 = prevInputs[i]; 10 | 11 | if (!((val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / val2)) || (val1 !== val1 && val2 !== val2))) { 12 | // eslint-disable-line 13 | return false; 14 | } 15 | } 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /src/core/magicSelf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import React from 'react'; 6 | import invariant from 'tiny-invariant'; 7 | 8 | // inject None effects 9 | React.PureComponent.prototype.componentDidMount = () => {}; 10 | React.Component.prototype.componentDidMount = () => {}; 11 | 12 | invariant(typeof Symbol === 'function' && Symbol.for, 'react-class-hooks needs Symbols!'); 13 | 14 | // Separate objects for better debugging. 15 | export const MAGIC_STATES = Symbol.for('magicStates'); 16 | export const MAGIC_EFFECTS = Symbol.for('magicEffects'); 17 | export const MAGIC_MEMOS = Symbol.for('magicMemos'); 18 | export const MAGIC_REFS = Symbol.for('magicRefs'); 19 | export const MAGIC_STACKS = Symbol.for('magicStacks'); 20 | 21 | const ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; 22 | 23 | const isReact15 = React.version.indexOf('15') === 0 24 | 25 | // React 15.3.2 support + Polyfill 26 | const instanceKey = isReact15 ? '_instance' : 'stateNode'; 27 | 28 | if (isReact15) { 29 | invariant( 30 | ReactInternals, 31 | 'Please for React ^15.3.2 - 15.6.2 import "react-class-hooks/poly15" in your root index.js!' 32 | ); 33 | } 34 | 35 | export function getMagicSelf() { 36 | invariant( 37 | ReactInternals.ReactCurrentOwner.current, 38 | 'You are using Hooks outside of "render" React.Component Method!' 39 | ); 40 | return ReactInternals.ReactCurrentOwner.current[instanceKey]; 41 | } 42 | 43 | export function getMagicFiber() { 44 | return ReactInternals.ReactCurrentOwner.current; 45 | } 46 | 47 | export const getMagicDispatcher = () => { 48 | return ReactInternals.ReactCurrentDispatcher.current; 49 | }; 50 | 51 | export function checkSymbol(name, keySymbol) { 52 | invariant(typeof keySymbol === 'symbol', `${name} - Expected a Symbol for key!`); 53 | } 54 | -------------------------------------------------------------------------------- /src/core/magicStack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { getMagicSelf, MAGIC_STACKS } from './magicSelf'; 6 | 7 | export function MagicStack(StackName) { 8 | this.name = StackName; 9 | this.symbol = Symbol(`${this.name}.Stack`); 10 | // this.cleanSymbol = Symbol(`${this.name}.Stack.Cleaner`); 11 | this.keys = []; 12 | 13 | this.getKey = stackIndex => { 14 | const len = this.keys.length; 15 | // create if not exist 16 | if (stackIndex > len) { 17 | for (let i = len; i < stackIndex; i += 1) this.keys.push(Symbol(`${this.name}-${i}`)); 18 | } 19 | return this.keys[stackIndex - 1]; 20 | }; 21 | } 22 | 23 | export function useMagicStack(magicStack, hook, ...args) { 24 | // inject next renders stack counter cleaner 25 | const self = getMagicSelf(); 26 | if (!self[MAGIC_STACKS]) { 27 | self[MAGIC_STACKS] = {}; 28 | const renderFunc = self.render.bind(self); 29 | self.render = (...arggs) => { 30 | Object.getOwnPropertySymbols(self[MAGIC_STACKS]).forEach(k => { 31 | self[MAGIC_STACKS][k] = 0; 32 | }); 33 | return renderFunc(...arggs); 34 | }; 35 | } 36 | 37 | // stack counter init 38 | if (!self[MAGIC_STACKS][magicStack.symbol]) { 39 | self[MAGIC_STACKS][magicStack.symbol] = 0; 40 | } 41 | 42 | // stack counter update 43 | self[MAGIC_STACKS][magicStack.symbol] += 1; 44 | 45 | return hook(magicStack.getKey(self[MAGIC_STACKS][magicStack.symbol]), ...args); 46 | } 47 | -------------------------------------------------------------------------------- /src/core/useClassCallback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { useClassMemoKey } from './useClassMemo'; 6 | import { createHook, createNamedHook } from './createHook'; 7 | 8 | export function useClassCallbackKey(keySymbol, callback, inputs) { 9 | return useClassMemoKey(keySymbol, () => callback, inputs); 10 | } 11 | 12 | export const useClassCallback = createHook('Callbacks', useClassCallbackKey); 13 | 14 | useClassCallback.create = name => createNamedHook(name, useClassCallbackKey); 15 | 16 | useClassCallback.createStack = stackName => createHook(stackName, useClassCallbackKey); 17 | -------------------------------------------------------------------------------- /src/core/useClassContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import invariant from 'tiny-invariant'; 6 | import { checkSymbol, getMagicDispatcher, getMagicSelf } from './magicSelf'; 7 | import { setDevToolsHookState } from './devTools'; 8 | import { createHook } from './createHook'; 9 | 10 | export function useClassContextKey(keySymbol, context, observedBits) { 11 | checkSymbol('useClassContext', keySymbol); 12 | getMagicSelf(); // invariant hook outside render method 13 | invariant(context && context.Provider && context.Consumer, 'Context should be React.createContext object!'); 14 | 15 | const contextValue = getMagicDispatcher().readContext(context, observedBits); 16 | 17 | setDevToolsHookState(keySymbol.description, contextValue); 18 | 19 | return contextValue; 20 | } 21 | 22 | export const useClassContext = createHook('Contexts', useClassContextKey); 23 | -------------------------------------------------------------------------------- /src/core/useClassDebugValue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { createHook } from './createHook'; 6 | import { checkSymbol } from './magicSelf'; 7 | import { setDevToolsHookState } from './devTools'; 8 | 9 | export function useClassDebugValueKey(keySymbol, value, formatter) { 10 | checkSymbol('useDebugValueKey', keySymbol); 11 | const viewValue = typeof formatter === 'function' ? formatter(value) : value; 12 | setDevToolsHookState(keySymbol.description, viewValue); 13 | } 14 | 15 | export const useClassDebugValue = createHook('DebugValue', useClassDebugValueKey); 16 | -------------------------------------------------------------------------------- /src/core/useClassEffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { createHook, createNamedHook } from './createHook'; 6 | import { useClassEffectKey } from './useClassEffectKey'; 7 | 8 | export const useClassEffect = createHook('Effects', useClassEffectKey); 9 | 10 | useClassEffect.create = name => createNamedHook(name, useClassEffectKey); 11 | 12 | useClassEffect.createStack = stackName => createHook(stackName, useClassEffectKey); 13 | 14 | /** ******************************************* alias - may confuse ? ********************************************** */ 15 | 16 | export function useClassDidMount(...args) { 17 | if (args && args.length > 1 && typeof args[0] === 'symbol') return useClassEffect(args[0], args[1], []); 18 | return useClassEffect(args[0], []); 19 | } 20 | 21 | export function useClassDidUpdate(...args) { 22 | if (args && args.length > 2 && typeof args[0] === 'symbol') return useClassEffect(args[0], args[1], args[2], true); 23 | return useClassEffect(args[0], args[1], true); 24 | } 25 | -------------------------------------------------------------------------------- /src/core/useClassEffectKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import invariant from 'tiny-invariant'; 6 | import { checkSymbol, getMagicSelf, MAGIC_EFFECTS } from './magicSelf'; 7 | import { inputsArrayEqual } from './inputsEqual'; 8 | 9 | export const useClassEffectKey = (keySymbol, creator, inputs, onlyDidUpdate = false) => { 10 | checkSymbol('useClassEffect', keySymbol); 11 | invariant(typeof creator === 'function', 'Creator should be a function!'); 12 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 13 | 14 | const self = getMagicSelf(); 15 | 16 | // create MAGIC_EFFECTS if not exists 17 | if (!self[MAGIC_EFFECTS]) self[MAGIC_EFFECTS] = {}; 18 | 19 | // First Render -> Assign creator, inputs and inject methods 20 | // TODO didCatch 21 | if (!self[MAGIC_EFFECTS].hasOwnProperty(keySymbol)) { 22 | self[MAGIC_EFFECTS][keySymbol] = { 23 | creator, 24 | inputs 25 | }; 26 | 27 | if (!onlyDidUpdate) { 28 | // inject componentDidMount 29 | const didMount = typeof self.componentDidMount === 'function' ? self.componentDidMount.bind(self) : undefined; 30 | self.componentDidMount = () => { 31 | if (didMount) didMount(); 32 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); 33 | // save last executed inputs 34 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 35 | invariant( 36 | !self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 37 | 'useClassEffect return (Effect Cleaner) should be Function or Void !' 38 | ); 39 | }; 40 | } 41 | 42 | // inject componentDidUpdate 43 | const didUpdate = typeof self.componentDidUpdate === 'function' ? self.componentDidUpdate.bind(self) : undefined; 44 | self.componentDidUpdate = (...args) => { 45 | if (didUpdate) didUpdate(...args); 46 | 47 | // execute if no inputs || inputs array has values and values changed 48 | const execute = 49 | !self[MAGIC_EFFECTS][keySymbol].inputs || 50 | !inputsArrayEqual(self[MAGIC_EFFECTS][keySymbol].inputs, self[MAGIC_EFFECTS][keySymbol].prevInputs); 51 | 52 | if (execute) { 53 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 54 | self[MAGIC_EFFECTS][keySymbol].cleaner = self[MAGIC_EFFECTS][keySymbol].creator(); 55 | // save last executed inputs! 56 | self[MAGIC_EFFECTS][keySymbol].prevInputs = self[MAGIC_EFFECTS][keySymbol].inputs; 57 | invariant( 58 | !self[MAGIC_EFFECTS][keySymbol].cleaner || typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function', 59 | 'useClassEffect return (Effect Cleaner) should be Function or Void !' 60 | ); 61 | } 62 | }; 63 | 64 | // inject componentWillUnmount 65 | const unmount = typeof self.componentWillUnmount === 'function' ? self.componentWillUnmount.bind(self) : undefined; 66 | self.componentWillUnmount = () => { 67 | if (unmount) unmount(); 68 | if (typeof self[MAGIC_EFFECTS][keySymbol].cleaner === 'function') self[MAGIC_EFFECTS][keySymbol].cleaner(); 69 | }; 70 | } else { 71 | // next renders 72 | self[MAGIC_EFFECTS][keySymbol].creator = creator; 73 | self[MAGIC_EFFECTS][keySymbol].inputs = inputs; 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/core/useClassImperativeHandle.js: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import { useClassEffect } from './useClassEffect'; 3 | 4 | export function useClassImperativeHandle(ref, create, deps) { 5 | invariant( 6 | typeof create === 'function', 7 | `Expected useImperativeHandle() second argument to be a function that creates a handle. Instead received: ${ 8 | create !== null ? typeof create : 'null' 9 | }` 10 | ); 11 | invariant( 12 | deps === null || deps === undefined || Array.isArray(deps), 13 | 'Hook received a final argument that is not an array!' 14 | ); 15 | 16 | const effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; 17 | 18 | // eslint-disable-next-line consistent-return 19 | useClassEffect(() => { 20 | if (typeof ref === 'function') { 21 | const refCallback = ref; 22 | refCallback(create()); 23 | // eslint-disable-next-line func-names 24 | return function() { 25 | refCallback(null); 26 | }; 27 | } 28 | if (ref !== null && ref !== undefined) { 29 | const refObject = ref; 30 | invariant( 31 | refObject.hasOwnProperty('current'), 32 | `Expected useImperativeHandle() first argument to either be a ref callback or React.createRef() object. Instead received: an object with keys {${Object.keys( 33 | refObject 34 | ).join(', ')}}` 35 | ); 36 | refObject.current = create(); 37 | // eslint-disable-next-line func-names 38 | return function() { 39 | refObject.current = null; 40 | }; 41 | } 42 | }, effectDeps); 43 | } 44 | -------------------------------------------------------------------------------- /src/core/useClassMemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import invariant from 'tiny-invariant'; 6 | import { checkSymbol, getMagicSelf, MAGIC_MEMOS } from './magicSelf'; 7 | import { inputsArrayEqual } from './inputsEqual'; 8 | import { createHook, createNamedHook } from './createHook'; 9 | import { setDevToolsHookState } from './devTools'; 10 | 11 | export const useClassMemoKey = (keySymbol, creator, inputs) => { 12 | checkSymbol('useClassMemo', keySymbol); 13 | invariant(typeof creator === 'function', 'Creator should be a function!'); 14 | invariant(!inputs || Array.isArray(inputs), 'inputs should be an array!'); 15 | 16 | const self = getMagicSelf(); 17 | 18 | // create magic Memos if not exists 19 | if (!self[MAGIC_MEMOS]) self[MAGIC_MEMOS] = {}; 20 | 21 | // First Render -> assign creator, inputs, value 22 | if (!self[MAGIC_MEMOS].hasOwnProperty(keySymbol)) { 23 | self[MAGIC_MEMOS][keySymbol] = { 24 | creator, 25 | inputs, 26 | value: creator() 27 | }; 28 | } else { 29 | // next renders 30 | let execute = false; 31 | if (!inputs) { 32 | if (creator !== self[MAGIC_MEMOS][keySymbol].creator) { 33 | execute = true; 34 | } 35 | } else { 36 | execute = !inputsArrayEqual(inputs, self[MAGIC_MEMOS][keySymbol].inputs); 37 | } 38 | if (execute) { 39 | self[MAGIC_MEMOS][keySymbol] = { 40 | creator, 41 | inputs, 42 | value: creator() 43 | }; 44 | } 45 | } 46 | 47 | const returnValue = self[MAGIC_MEMOS][keySymbol].value; 48 | setDevToolsHookState(keySymbol.description, returnValue); 49 | return returnValue; 50 | }; 51 | 52 | export const useClassMemo = createHook('Memos', useClassMemoKey); 53 | 54 | useClassMemo.create = name => createNamedHook(name, useClassMemoKey); 55 | 56 | useClassMemo.createStack = stackName => createHook(stackName, useClassMemoKey); 57 | -------------------------------------------------------------------------------- /src/core/useClassReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { useClassStateKey } from './useClassStateKey'; 6 | import { createNamedHook, createHook } from './createHook'; 7 | 8 | export const useClassReducerKey = (keySymbol, reducer, initialState) => { 9 | const stateSetState = useClassStateKey(keySymbol, initialState); 10 | const state = stateSetState[0]; 11 | const setState = stateSetState[1]; 12 | 13 | function dispatch(action) { 14 | const nextState = reducer(state, action); 15 | setState(nextState); 16 | } 17 | 18 | return [state, dispatch]; 19 | }; 20 | 21 | export const useClassReducer = createHook('Reducers', useClassReducerKey); 22 | 23 | useClassReducer.create = name => createNamedHook(name, useClassReducerKey); 24 | -------------------------------------------------------------------------------- /src/core/useClassRef.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { createHook, createNamedHook } from './createHook'; 6 | import { useClassRefKey } from './useClassRefKey'; 7 | 8 | export const useClassRef = createHook('Refs', useClassRefKey); 9 | 10 | useClassRef.create = name => createNamedHook(name, useClassRefKey); 11 | 12 | useClassRef.createStack = stackName => createHook(stackName, useClassRefKey); 13 | 14 | // poly 15 ref 15 | export const refCallback = refObject => ref => { 16 | refObject.current = ref; 17 | }; 18 | -------------------------------------------------------------------------------- /src/core/useClassRefKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { checkSymbol, getMagicSelf, MAGIC_REFS } from './magicSelf'; 6 | import { setDevToolsHookState } from './devTools'; 7 | 8 | export function useClassRefKey(keySymbol, initialValue) { 9 | checkSymbol('useClassRefKey', keySymbol); 10 | 11 | const self = getMagicSelf(); 12 | 13 | // first time Render && first Hook 14 | if (!self[MAGIC_REFS]) self[MAGIC_REFS] = {}; 15 | 16 | // first time Render -> assign initial Value 17 | if (!self[MAGIC_REFS].hasOwnProperty(keySymbol)) { 18 | const ref = { current: initialValue }; 19 | Object.seal(ref); 20 | self[MAGIC_REFS][keySymbol] = ref; 21 | } 22 | 23 | const returnValue = self[MAGIC_REFS][keySymbol]; 24 | setDevToolsHookState(keySymbol.description, returnValue); 25 | return returnValue; 26 | } 27 | -------------------------------------------------------------------------------- /src/core/useClassState.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { createHook, createNamedHook } from './createHook'; 6 | import { useClassStateKey } from './useClassStateKey'; 7 | 8 | export const useClassState = createHook('States', useClassStateKey); 9 | 10 | useClassState.create = name => createNamedHook(name, useClassStateKey); 11 | 12 | useClassState.createStack = stackName => createHook(stackName, useClassStateKey); 13 | -------------------------------------------------------------------------------- /src/core/useClassStateKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { getMagicSelf, checkSymbol, MAGIC_STATES } from './magicSelf'; 6 | import { setDevToolsHookState } from './devTools'; 7 | 8 | export function useClassStateKey(keySymbol, initialValue) { 9 | checkSymbol('useClassStateKey', keySymbol); 10 | 11 | const self = getMagicSelf(); 12 | 13 | // first time Render && first Hook 14 | if (!self[MAGIC_STATES]) self[MAGIC_STATES] = {}; 15 | 16 | // first time Render -> assign initial Value and create Setter 17 | if (!self[MAGIC_STATES].hasOwnProperty(keySymbol)) { 18 | self[MAGIC_STATES][keySymbol] = { 19 | value: typeof initialValue === 'function' ? initialValue() : initialValue, 20 | setValue: (value, callback) => { 21 | const newState = typeof value === 'function' ? value(self[MAGIC_STATES][keySymbol].value) : value; 22 | if (self[MAGIC_STATES][keySymbol].value !== newState) { 23 | self[MAGIC_STATES][keySymbol].value = newState; 24 | if (self.updater.isMounted(self)) { 25 | self.updater.enqueueForceUpdate(self, callback); 26 | } 27 | } 28 | } 29 | }; 30 | } 31 | 32 | const { value, setValue } = self[MAGIC_STATES][keySymbol]; 33 | setDevToolsHookState(keySymbol.description, value); 34 | return [value, setValue]; 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/salvoravida/react-class-hooks 3 | */ 4 | 5 | import { useClassState } from './core/useClassState'; 6 | import { useClassEffect } from './core/useClassEffect'; 7 | import { useClassMemo } from './core/useClassMemo'; 8 | import { useClassCallback } from './core/useClassCallback'; 9 | import { useClassReducer } from './core/useClassReducer'; 10 | import { useClassRef, refCallback } from './core/useClassRef'; 11 | import { useClassContext } from './core/useClassContext'; 12 | import { useClassImperativeHandle } from './core/useClassImperativeHandle'; 13 | import { useClassDebugValue } from './core/useClassDebugValue'; 14 | import { supportReactDevTools } from './core/devTools'; 15 | 16 | const useClassLayoutEffect = useClassEffect; 17 | 18 | export { 19 | useClassState, 20 | useClassEffect, 21 | useClassLayoutEffect, 22 | useClassMemo, 23 | useClassCallback, 24 | useClassReducer, 25 | useClassRef, 26 | refCallback, 27 | useClassContext, 28 | useClassImperativeHandle, 29 | useClassDebugValue, 30 | supportReactDevTools 31 | }; 32 | --------------------------------------------------------------------------------