Last active
May 29, 2020 17:13
-
-
Save joejag/3d78a197cb53287067a4eacab05057db to your computer and use it in GitHub Desktop.
This file contains 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
import useWhyRerender from './whyRerender' | |
const MyComponent = props => { | |
// any states | |
const [error, setError] = React.useState(false) | |
// add interesting things to track | |
useWhyRerender({ | |
...props, | |
error | |
}) | |
return (<div />) | |
} |
This file contains 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
import React from 'react' | |
// START COPY! | |
// Copy of rdiff from https://github.com/cosmicanant/recursive-diff/blob/master/src | |
// START COPY! | |
const instanceOf = instance => x => x instanceof instance | |
const isNumber = x => typeof x === 'number' | |
const isBoolean = x => typeof x === 'boolean' | |
const isString = x => typeof x === 'string' | |
const isDate = instanceOf(Date) | |
const isUndefined = x => typeof x === 'undefined' | |
const isNull = x => x === null | |
const isArray = instanceOf(Array) | |
const isMap = instanceOf(Map) | |
const isSet = instanceOf(Set) | |
const isIterableObject = x => { | |
const type = Object.prototype.toString.call(x) | |
return type === '[object Object]' | |
} | |
const noop = () => {} | |
const areDatesEqual = (dt1, dt2) => dt1.getTime() === dt2.getTime() | |
function setValueByPath(x, path = [], value, visitorCallback) { | |
if (!isArray(path)) { | |
throw new Error(`Diff path: "${path}" is not valid`) | |
} | |
const { length } = path | |
if (length === 0) { | |
return value | |
} | |
let val = x | |
for (let i = 0; i < length; i += 1) { | |
const key = path[i] | |
if (!val) | |
throw new Error( | |
`Invalid path: "${path}" for object: ${JSON.stringify(x, null, 2)}` | |
) | |
else if (key == null) | |
throw new Error( | |
`Invalid path: "${path}" for object: ${JSON.stringify(x, null, 2)}` | |
) | |
if (i !== length - 1) { | |
val = val[key] | |
if (visitorCallback) { | |
visitorCallback(val) | |
} | |
} else { | |
val[key] = value | |
} | |
} | |
return x | |
} | |
function deleteValueByPath(ob, path) { | |
const keys = path || [] | |
if (keys.length === 0) { | |
return undefined | |
} | |
let val = ob | |
const { length } = keys | |
for (let i = 0; i < length; i += 1) { | |
if (i !== length - 1) { | |
if (!val[keys[i]]) { | |
throw new Error( | |
`Invalid path: "${path}" for object: ${JSON.stringify(ob, null, 2)}` | |
) | |
} | |
val = val[keys[i]] | |
} else if (isIterableObject(val)) { | |
delete val[keys[i]] | |
} else { | |
const index = parseInt(keys[i], 10) | |
while (val.length > index) { | |
val.pop() | |
} | |
} | |
} | |
return ob | |
} | |
const utils = { | |
isNumber, | |
isBoolean, | |
isString, | |
isDate, | |
isUndefined, | |
isNull, | |
isArray, | |
isMap, | |
isSet, | |
isIterableObject, | |
noop, | |
areDatesEqual, | |
setValueByPath, | |
deleteValueByPath | |
} | |
const types = { | |
NUMBER: 'NUMBER', | |
BOOLEAN: 'BOOLEAN', | |
STRING: 'STRING', | |
NULL: 'NULL', | |
UNDEFINED: 'UNDEFINED', | |
DATE: 'DATE', | |
ARRAY: 'ARRAY', | |
MAP: 'MAP', | |
SET: 'SET', | |
ITERABLE_OBJECT: 'ITERABLE_OBJECT', | |
DEFAULT: 'OBJECT' | |
} | |
const checkType = { | |
[types.NUMBER]: utils.isNumber, | |
[types.BOOLEAN]: utils.isBoolean, | |
[types.STRING]: utils.isString, | |
[types.DATE]: utils.isDate, | |
[types.UNDEFINED]: utils.isUndefined, | |
[types.NULL]: utils.isNull, | |
[types.ARRAY]: utils.isArray, | |
[types.MAP]: utils.isMap, | |
[types.SET]: utils.isSet, | |
[types.ITERABLE_OBJECT]: utils.isIterableObject | |
} | |
const checkEqualityForComplexTypes = { | |
[types.DATE]: utils.areDatesEqual | |
} | |
const iterableTypes = [types.ITERABLE_OBJECT, types.MAP, types.ARRAY, types.SET] | |
function getType(x) { | |
const keys = Object.keys(checkType) | |
let type = types.DEFAULT | |
for (let i = 0; i < keys.length; i += 1) { | |
if (checkType[keys[i]](x)) { | |
type = keys[i] | |
break | |
} | |
} | |
return type | |
} | |
function isTraversalNeeded(type1, type2) { | |
return type1 === type2 && iterableTypes.indexOf(type1) >= 0 | |
} | |
function areEqual(x, y, type1, type2) { | |
if (type1 !== type2) { | |
return false | |
} | |
return checkEqualityForComplexTypes[type1] | |
? checkEqualityForComplexTypes[type1](x, y) | |
: x === y | |
} | |
function computeOp(x, y, type1, type2) { | |
let op | |
if (type1 === types.UNDEFINED && type2 !== types.UNDEFINED) { | |
op = 'add' | |
} else if (type1 !== types.UNDEFINED && type2 === types.UNDEFINED) { | |
op = 'delete' | |
} else if (!areEqual(x, y, type1, type2)) { | |
op = 'update' | |
} else { | |
utils.noop() | |
} | |
return op | |
} | |
function getKeys(x, y, type) { | |
let keys | |
if (type === types.ITERABLE_OBJECT) { | |
keys = new Set(Object.keys(x).concat(Object.keys(y))) | |
} else if (type === types.ARRAY) { | |
keys = x.length > y.length ? new Array(x.length) : new Array(y.length) | |
keys = keys.fill(0, 0) | |
keys = keys.map((el, i) => i) | |
keys = new Set(keys) | |
} | |
return keys | |
} | |
function makeDiff(x, y, op, path, keepOldVal) { | |
const diffOb = { | |
op, | |
path | |
} | |
if (op === 'add' || op === 'update') { | |
diffOb.val = y | |
} | |
if (keepOldVal && op !== 'add') { | |
diffOb.oldVal = x | |
} | |
return diffOb | |
} | |
function privateGetDiff(x, y, keepOldVal, path, diff) { | |
const type1 = getType(x) | |
const type2 = getType(y) | |
const currPath = path || [] | |
const currDiff = diff || [] | |
if (isTraversalNeeded(type1, type2)) { | |
const iterator = getKeys(x, y, type1).values() | |
let key = iterator.next().value | |
while (key != null) { | |
privateGetDiff(x[key], y[key], keepOldVal, currPath.concat(key), currDiff) | |
key = iterator.next().value | |
} | |
} else { | |
const op = computeOp(x, y, type1, type2) | |
if (op != null) { | |
currDiff.push(makeDiff(x, y, op, path, keepOldVal)) | |
} | |
} | |
return currDiff | |
} | |
const getDiff = (x, y, keepOldValInDiff = false) => { | |
return privateGetDiff(x, y, keepOldValInDiff) | |
} | |
// END COPY! | |
// Copy of rdiff from https://github.com/cosmicanant/recursive-diff/blob/master/src | |
// END COPY! | |
const useWhyRerender = props => { | |
const ref = React.useRef() | |
React.useEffect(() => { | |
ref.current = props | |
}) | |
const prevProps = ref.current | |
if (prevProps === undefined) return | |
const differences = getDiff(prevProps, props) | |
if (differences.length === 0) { | |
global.console.group('🔥 Rerender detected! No differences') | |
} else { | |
global.console.group( | |
`🔥 Rerender detected! ${differences.length} difference(s)` | |
) | |
} | |
// Emoji list: http://www.unicode.org/emoji/charts/full-emoji-list.html | |
const emojiForOp = { | |
add: '➕', | |
update: '🖊', | |
remove: '➖' | |
} | |
differences.forEach(diff => | |
global.console.log( | |
emojiForOp[diff.op] || diff.op, | |
diff.path.join('.'), | |
'to', | |
diff.val | |
) | |
) | |
global.console.groupEnd() | |
} | |
export default useWhyRerender |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment