Skip to content

Instantly share code, notes, and snippets.

@urugator
Last active April 16, 2019 17:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save urugator/5c78da03a7b1a7682919cc1cf68ff8e9 to your computer and use it in GitHub Desktop.
Save urugator/5c78da03a7b1a7682919cc1cf68ff8e9 to your computer and use it in GitHub Desktop.
useDerivedState
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;
}
@jedwards1211
Copy link

jedwards1211 commented Apr 16, 2019

Ah I had overlooked the link to this in the issue thread. Very nice!

You could shorten the code in this way:

  const stateRef = React.useRef();
  const prevDepsRef = React.useRef();

  if (!prevDepsRef.current || !areHookInputsEqual(deps, prevDepsRef.current)) {
    stateRef.current = deriveState();
    prevDepsRef.current = deps;
  }   

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment