Skip to content

Instantly share code, notes, and snippets.

@joejag
Last active May 29, 2020 17:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joejag/3d78a197cb53287067a4eacab05057db to your computer and use it in GitHub Desktop.
Save joejag/3d78a197cb53287067a4eacab05057db to your computer and use it in GitHub Desktop.
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 />)
}
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