├── .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 [](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 | setLogger(logger + 1)}>
164 | Add Hidden LoggerCounter
165 |
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 |
setCount(count + 1)}>Add Counter 1
190 | {count % 5 === 0 && this.renderLogger()}
191 |
Other Anonymous Counter {count2}
192 |
setCount2(count2 + 1)}>Add Other Counter
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 |
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 |
--------------------------------------------------------------------------------