Skip to content

Commit

Permalink
fix(link): cancel idle callback on unmount (#22072)
Browse files Browse the repository at this point in the history
Co-authored-by: mAAdhaTTah <jamesorodig@gmail.com>
  • Loading branch information
Timer and mAAdhaTTah committed Feb 11, 2021
1 parent 27b6dd6 commit 5f41abd
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 11 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -50,6 +50,7 @@
"@opentelemetry/plugin-http": "0.14.0",
"@opentelemetry/plugin-https": "0.14.0",
"@opentelemetry/tracing": "0.14.0",
"@testing-library/react": "11.2.5",
"@types/cheerio": "0.22.16",
"@types/fs-extra": "8.1.0",
"@types/http-proxy": "1.17.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/client/experimental-script.tsx
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useContext } from 'react'
import { ScriptHTMLAttributes } from 'react'
import { HeadManagerContext } from '../next-server/lib/head-manager-context'
import { DOMAttributeNames } from './head-manager'
import requestIdleCallback from './request-idle-callback'
import { requestIdleCallback } from './request-idle-callback'

const ScriptCache = new Map()
const LoadCache = new Set()
Expand Down
9 changes: 7 additions & 2 deletions packages/next/client/request-idle-callback.ts
Expand Up @@ -13,10 +13,11 @@ declare global {
callback: (deadline: RequestIdleCallbackDeadline) => void,
opts?: RequestIdleCallbackOptions
) => RequestIdleCallbackHandle
cancelIdleCallback: (id: RequestIdleCallbackHandle) => void
}
}

const requestIdleCallback =
export const requestIdleCallback =
(typeof self !== 'undefined' && self.requestIdleCallback) ||
function (
cb: (deadline: RequestIdleCallbackDeadline) => void
Expand All @@ -32,4 +33,8 @@ const requestIdleCallback =
}, 1)
}

export default requestIdleCallback
export const cancelIdleCallback =
(typeof self !== 'undefined' && self.cancelIdleCallback) ||
function (id: RequestIdleCallbackHandle) {
return clearTimeout(id)
}
2 changes: 1 addition & 1 deletion packages/next/client/route-loader.ts
@@ -1,7 +1,7 @@
import { ComponentType } from 'react'
import { ClientBuildManifest } from '../build/webpack/plugins/build-manifest-plugin'
import getAssetPathFromRoute from '../next-server/lib/router/utils/get-asset-path-from-route'
import requestIdleCallback from './request-idle-callback'
import { requestIdleCallback } from './request-idle-callback'

// 3.8s was arbitrarily chosen as it's what https://web.dev/interactive
// considers as "Good" time-to-interactive. We must assume something went
Expand Down
10 changes: 8 additions & 2 deletions packages/next/client/use-intersection.tsx
@@ -1,5 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import requestIdleCallback from './request-idle-callback'
import {
requestIdleCallback,
cancelIdleCallback,
} from './request-idle-callback'

type UseIntersectionObserverInit = Pick<IntersectionObserverInit, 'rootMargin'>
type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit
Expand Down Expand Up @@ -43,7 +46,10 @@ export function useIntersection<T extends Element>({

useEffect(() => {
if (!hasIntersectionObserver) {
if (!visible) requestIdleCallback(() => setVisible(true))
if (!visible) {
const idleCallback = requestIdleCallback(() => setVisible(true))
return () => cancelIdleCallback(idleCallback)
}
}
}, [visible])

Expand Down
2 changes: 1 addition & 1 deletion packages/next/compiled/strip-ansi/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions test/integration/build-output/test/index.test.js
Expand Up @@ -94,14 +94,14 @@ describe('Build Output', () => {
expect(parseFloat(indexSize) - 266).toBeLessThanOrEqual(0)
expect(indexSize.endsWith('B')).toBe(true)

// should be no bigger than 63.8 kb
expect(parseFloat(indexFirstLoad)).toBeCloseTo(63.8, 1)
// should be no bigger than 63.9 kb
expect(parseFloat(indexFirstLoad)).toBeCloseTo(63.9, 1)
expect(indexFirstLoad.endsWith('kB')).toBe(true)

expect(parseFloat(err404Size) - 3.7).toBeLessThanOrEqual(0)
expect(err404Size.endsWith('kB')).toBe(true)

expect(parseFloat(err404FirstLoad)).toBeCloseTo(67, 1)
expect(parseFloat(err404FirstLoad)).toBeCloseTo(67.1, 0)
expect(err404FirstLoad.endsWith('kB')).toBe(true)

expect(parseFloat(sharedByAll)).toBeCloseTo(63.6, 1)
Expand Down
2 changes: 1 addition & 1 deletion test/integration/size-limit/test/index.test.js
Expand Up @@ -81,6 +81,6 @@ describe('Production response size', () => {
const delta = responseSizesBytes / 1024

// Expected difference: < 0.5
expect(delta).toBeCloseTo(284.1, 0)
expect(delta).toBeCloseTo(284.7, 0)
})
})
33 changes: 33 additions & 0 deletions test/unit/link-warnings.test.js
@@ -0,0 +1,33 @@
/**
* @jest-environment jsdom
*/
import { act, render } from '@testing-library/react'
import Link from 'next/link'

describe('<Link/>', () => {
let spy
beforeAll(() => {
spy = jest.spyOn(console, 'error').mockImplementation(() => {})
})

it('test link with unmount', () => {
act(() => {
const { unmount } = render(<Link href="/">hello</Link>)
unmount()
})

expect(spy).not.toHaveBeenCalled()
})

it('test link without unmount', () => {
act(() => {
render(<Link href="/">hello</Link>)
})

expect(spy).not.toHaveBeenCalled()
})

afterAll(() => {
spy.mockRestore()
})
})
93 changes: 93 additions & 0 deletions yarn.lock
Expand Up @@ -958,6 +958,14 @@
pirates "^4.0.0"
source-map-support "^0.5.16"

"@babel/runtime-corejs3@^7.10.2":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz#53d09813b7c20d616caf258e9325550ff701c039"
integrity sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"

"@babel/runtime-corejs3@^7.12.1":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4"
Expand All @@ -973,6 +981,13 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3", "@babel/template@^7.4.0":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
Expand Down Expand Up @@ -1551,6 +1566,17 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"

"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"

"@lerna/add@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.14.0.tgz#799d416e67d48c285967abf883be746557aefa48"
Expand Down Expand Up @@ -2604,12 +2630,39 @@
dependencies:
chokidar "^1.7.0"

"@testing-library/dom@^7.28.1":
version "7.29.4"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c"
integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
chalk "^4.1.0"
dom-accessibility-api "^0.5.4"
lz-string "^1.4.4"
pretty-format "^26.6.2"

"@testing-library/react@11.2.5":
version "11.2.5"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9"
integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^7.28.1"

"@types/amphtml-validator@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/amphtml-validator/-/amphtml-validator-1.0.0.tgz#9d4e0c879642938bbe5f363d49cafc8ae9f57c81"
dependencies:
"@types/node" "*"

"@types/aria-query@^4.2.0":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b"
integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==

"@types/async-retry@1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.4.2.tgz#7f910188cd3893b51e32df51765ee8d5646053e3"
Expand Down Expand Up @@ -2811,6 +2864,13 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"

"@types/istanbul-reports@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821"
integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==
dependencies:
"@types/istanbul-lib-report" "*"

"@types/jest-diff@*":
version "24.3.0"
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-24.3.0.tgz#29e237a3d954babfe6e23cc59b57ecd8ca8d858d"
Expand Down Expand Up @@ -3578,6 +3638,14 @@ args@4.0.0:
leven "2.1.0"
mri "1.1.0"

aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
dependencies:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"

arity-n@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745"
Expand Down Expand Up @@ -6072,6 +6140,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"

dom-accessibility-api@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==

dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
Expand Down Expand Up @@ -10244,6 +10317,11 @@ lru_map@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"

lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=

macos-release@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f"
Expand Down Expand Up @@ -12943,6 +13021,16 @@ pretty-format@^26.0.1:
ansi-styles "^4.0.0"
react-is "^16.12.0"

pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
dependencies:
"@jest/types" "^26.6.2"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^17.0.1"

pretty-hrtime@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
Expand Down Expand Up @@ -13248,6 +13336,11 @@ react-is@16.13.1, react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-i
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"

react-is@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==

react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
Expand Down

0 comments on commit 5f41abd

Please sign in to comment.