Last active
April 16, 2019 17:45
-
-
Save urugator/5c78da03a7b1a7682919cc1cf68ff8e9 to your computer and use it in GitHub Desktop.
useDerivedState
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const React = require('react'); | |
const __DEV__ = process.env.NODE_ENV !== 'production'; | |
const HOOK_NAME = 'useDerivedState'; | |
const NO_DEPS_HINT = 'If there are no dependencies, use "const [state] = useState(fn)"' | |
/** | |
* Copied from from React sources and adjusted | |
* inlined Object.is polyfill to avoid requiring consumers ship their own | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is | |
*/ | |
function is(x, y) { | |
return ( | |
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare | |
); | |
} | |
// Copied from React sources and adjusted | |
function areHookInputsEqual(nextDeps, prevDeps) { | |
if (__DEV__) { | |
// Don't bother comparing lengths in prod because these arrays should be | |
// passed inline. | |
if (nextDeps.length !== prevDeps.length) { | |
console.error( | |
'Warning: ' + | |
'The final argument passed to %s changed size between renders. The ' + | |
'order and size of this array must remain constant.\n\n' + | |
'Previous: %s\n' + | |
'Incoming: %s', | |
HOOK_NAME, | |
`[${nextDeps.join(', ')}]`, | |
`[${prevDeps.join(', ')}]`, | |
); | |
} | |
} | |
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { | |
if (is(nextDeps[i], prevDeps[i])) { | |
continue; | |
} | |
return false; | |
} | |
return true; | |
} | |
module.exports = function useDerivedState(deriveState, deps) { | |
if (__DEV__) { | |
if (typeof deriveState !== 'function') { | |
console.error( | |
'Warning: The first argument of %s must be a function, received: %s', | |
HOOK_NAME, | |
deriveState | |
); | |
} | |
if (!Array.isArray(deps)) { | |
console.error( | |
'Warning: The final argument of %s must be an array, received: %s\n' + NO_DEPS_HINT, | |
HOOK_NAME, | |
deps | |
); | |
} | |
if (deps.length === 0) { | |
console.error( | |
'Warning: The array of dependencies passed to %s must not be empty\n' + NO_DEPS_HINT, | |
HOOK_NAME | |
); | |
} | |
} | |
const stateRef = React.useRef(); | |
const prevDepsRef = React.useRef(deps); | |
if (deps === prevDepsRef.current) { | |
// first run | |
stateRef.current = deriveState(); | |
} else if (!areHookInputsEqual(deps, prevDepsRef.current)) { | |
stateRef.current = deriveState(); | |
prevDepsRef.current = deps; | |
} | |
return stateRef.current; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ah I had overlooked the link to this in the issue thread. Very nice!
You could shorten the code in this way: