Switch callbacks to React.useCallback (#7160)

* upgrade to eslint-plugin-react@7.26.1

* switch callbacks to React.useCallback
This commit is contained in:
chris48s
2021-10-17 11:42:08 +01:00
committed by GitHub
parent a43c98a89a
commit 4109d36ad9
12 changed files with 315 additions and 196 deletions

View File

@@ -43,9 +43,12 @@ function Example({
exampleData: RenderableExample
isBadgeSuggestion: boolean
}): JSX.Element {
function handleClick(): void {
onClick(exampleData, isBadgeSuggestion)
}
const handleClick = React.useCallback(
function (): void {
onClick(exampleData, isBadgeSuggestion)
},
[exampleData, isBadgeSuggestion, onClick]
)
let exampleUrl, previewUrl
if (isBadgeSuggestion) {

View File

@@ -50,13 +50,16 @@ function _CopiedContentIndicator(
},
}))
function handlePoseComplete(): void {
if (pose === 'effectStart') {
setPose('effectEnd')
} else {
setPose('hidden')
}
}
const handlePoseComplete = React.useCallback(
function (): void {
if (pose === 'effectStart') {
setPose('effectEnd')
} else {
setPose('hidden')
}
},
[pose, setPose]
)
return (
<ContentAnchor>

View File

@@ -40,10 +40,13 @@ export default function Customizer({
const [markup, setMarkup] = useState<string>()
const [message, setMessage] = useState<string>()
function generateBuiltBadgeUrl(): string {
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl}${path}${suffix}`
}
const generateBuiltBadgeUrl = React.useCallback(
function (): string {
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl}${path}${suffix}`
},
[baseUrl, path, queryString]
)
function renderLivePreview(): JSX.Element {
// There are some usability issues here. It would be better if the message
@@ -67,28 +70,31 @@ export default function Customizer({
)
}
async function copyMarkup(markupFormat: MarkupFormat): Promise<void> {
const builtBadgeUrl = generateBuiltBadgeUrl()
const markup = generateMarkup({
badgeUrl: builtBadgeUrl,
link,
title,
markupFormat,
})
const copyMarkup = React.useCallback(
async function (markupFormat: MarkupFormat): Promise<void> {
const builtBadgeUrl = generateBuiltBadgeUrl()
const markup = generateMarkup({
badgeUrl: builtBadgeUrl,
link,
title,
markupFormat,
})
try {
await clipboardCopy(markup)
} catch (e) {
setMessage('Copy failed')
setMarkup(markup)
return
}
try {
await clipboardCopy(markup)
} catch (e) {
setMessage('Copy failed')
setMarkup(markup)
return
}
setMarkup(markup)
if (indicatorRef.current) {
indicatorRef.current.trigger()
}
}
if (indicatorRef.current) {
indicatorRef.current.trigger()
}
},
[generateBuiltBadgeUrl, link, title, setMessage, setMarkup]
)
function renderMarkupAndLivePreview(): JSX.Element {
return (
@@ -110,26 +116,32 @@ export default function Customizer({
)
}
function handlePathChange({
path,
isComplete,
}: {
path: string
isComplete: boolean
}): void {
setPath(path)
setPathIsComplete(isComplete)
}
const handlePathChange = React.useCallback(
function ({
path,
isComplete,
}: {
path: string
isComplete: boolean
}): void {
setPath(path)
setPathIsComplete(isComplete)
},
[setPath, setPathIsComplete]
)
function handleQueryStringChange({
queryString,
isComplete,
}: {
queryString: string
isComplete: boolean
}): void {
setQueryString(queryString)
}
const handleQueryStringChange = React.useCallback(
function ({
queryString,
isComplete,
}: {
queryString: string
isComplete: boolean
}): void {
setQueryString(queryString)
},
[setQueryString]
)
return (
<form action="">

View File

@@ -149,14 +149,17 @@ export default function PathBuilder({
}
}, [tokens, namedParams, onChange])
function handleTokenChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setNamedParams({
...namedParams,
[name]: value,
})
}
const handleTokenChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setNamedParams({
...namedParams,
[name]: value,
})
},
[setNamedParams, namedParams]
)
function renderLiteral(
literal: string,

View File

@@ -270,18 +270,24 @@ export default function QueryStringBuilder({
}, {} as Record<BadgeOptionName, string>)
)
function handleServiceQueryParamChange({
target: { name, type: targetType, checked, value },
}: ChangeEvent<HTMLInputElement>): void {
const outValue = targetType === 'checkbox' ? checked : value
setQueryParams({ ...queryParams, [name]: outValue })
}
const handleServiceQueryParamChange = React.useCallback(
function ({
target: { name, type: targetType, checked, value },
}: ChangeEvent<HTMLInputElement>): void {
const outValue = targetType === 'checkbox' ? checked : value
setQueryParams({ ...queryParams, [name]: outValue })
},
[setQueryParams, queryParams]
)
function handleBadgeOptionChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement>): void {
setBadgeOptions({ ...badgeOptions, [name]: value })
}
const handleBadgeOptionChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>): void {
setBadgeOptions({ ...badgeOptions, [name]: value })
},
[setBadgeOptions, badgeOptions]
)
useEffect(() => {
if (onChange) {

View File

@@ -86,24 +86,30 @@ export default function GetMarkupButton({
Select<Option>
>
async function onControlMouseDown(event: MouseEvent): Promise<void> {
if (onMarkupRequested) {
await onMarkupRequested('link')
}
if (selectRef.current) {
selectRef.current.blur()
}
}
const onControlMouseDown = React.useCallback(
async function (event: MouseEvent): Promise<void> {
if (onMarkupRequested) {
await onMarkupRequested('link')
}
if (selectRef.current) {
selectRef.current.blur()
}
},
[onMarkupRequested, selectRef]
)
async function onOptionClick(
// Eeesh.
value: Option | readonly Option[] | null | undefined
): Promise<void> {
const { value: markupFormat } = value as Option
if (onMarkupRequested) {
await onMarkupRequested(markupFormat)
}
}
const onOptionClick = React.useCallback(
async function onOptionClick(
// Eeesh.
value: Option | readonly Option[] | null | undefined
): Promise<void> {
const { value: markupFormat } = value as Option
if (onMarkupRequested) {
await onMarkupRequested(markupFormat)
}
},
[onMarkupRequested]
)
return (
// TODO It doesn't seem to be possible to check the types and wrap with

View File

@@ -44,33 +44,39 @@ export default function DynamicBadgeMaker({
const isValid =
values.datatype && values.label && values.dataUrl && values.query
function onChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
}
const onChange = React.useCallback(
function ({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
},
[values]
)
function onSubmit(e: React.FormEvent): void {
e.preventDefault()
const onSubmit = React.useCallback(
function onSubmit(e: React.FormEvent): void {
e.preventDefault()
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
window.open(
dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
color,
prefix,
suffix,
}),
'_blank'
)
}
const { datatype, label, dataUrl, query, color, prefix, suffix } = values
window.open(
dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
color,
prefix,
suffix,
}),
'_blank'
)
},
[baseUrl, values]
)
return (
<form onSubmit={onSubmit}>

View File

@@ -54,24 +54,28 @@ export default function Main({
const searchTimeout = useRef(0)
const baseUrl = getBaseUrl()
function performSearch(query: string): void {
setSearchIsInProgress(false)
const performSearch = React.useCallback(
function (query: string): void {
setSearchIsInProgress(false)
setQueryIsTooShort(query.length === 1)
setQueryIsTooShort(query.length === 1)
if (query.length >= 2) {
const flat = ServiceDefinitionSetHelper.create(services)
.notDeprecated()
.search(query)
.toArray()
setSearchResults(groupBy(flat, 'category'))
} else {
setSearchResults(undefined)
}
}
if (query.length >= 2) {
const flat = ServiceDefinitionSetHelper.create(services)
.notDeprecated()
.search(query)
.toArray()
setSearchResults(groupBy(flat, 'category'))
} else {
setSearchResults(undefined)
}
},
[setSearchIsInProgress, setQueryIsTooShort, setSearchResults]
)
function searchQueryChanged(query: string): void {
/*
const searchQueryChanged = React.useCallback(
function (query: string): void {
/*
Add a small delay before showing search results
so that we wait until the user has stopped typing
before we start loading stuff.
@@ -81,22 +85,27 @@ export default function Main({
b) stops the page from 'flashing' as the user types, like this:
https://user-images.githubusercontent.com/7288322/42600206-9b278470-85b5-11e8-9f63-eb4a0c31cb4a.gif
*/
setSearchIsInProgress(true)
window.clearTimeout(searchTimeout.current)
searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
}
setSearchIsInProgress(true)
window.clearTimeout(searchTimeout.current)
searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
},
[setSearchIsInProgress, performSearch]
)
function exampleClicked(
example: RenderableExample,
isSuggestion: boolean
): void {
setSelectedExample(example)
setSelectedExampleIsSuggestion(isSuggestion)
}
const exampleClicked = React.useCallback(
function (example: RenderableExample, isSuggestion: boolean): void {
setSelectedExample(example)
setSelectedExampleIsSuggestion(isSuggestion)
},
[setSelectedExample, setSelectedExampleIsSuggestion]
)
function dismissMarkupModal(): void {
setSelectedExample(undefined)
}
const dismissMarkupModal = React.useCallback(
function (): void {
setSelectedExample(undefined)
},
[setSelectedExample]
)
function Category({
category,

View File

@@ -18,21 +18,27 @@ export default function StaticBadgeMaker({
const isValid = values.message && values.color
function onChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
}
const onChange = React.useCallback(
function onChange({
target: { name, value },
}: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
setValues({
...values,
[name]: value,
})
},
[setValues, values]
)
function onSubmit(e: React.FormEvent): void {
e.preventDefault()
const onSubmit = React.useCallback(
function (e: React.FormEvent): void {
e.preventDefault()
const { label, message, color } = values
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
}
const { label, message, color } = values
window.open(staticBadgeUrl({ baseUrl, label, message, color }), '_blank')
},
[baseUrl, values]
)
return (
<form onSubmit={onSubmit}>

View File

@@ -41,41 +41,47 @@ export default function SuggestionAndSearch({
const [projectUrl, setProjectUrl] = useState<string>()
const [suggestions, setSuggestions] = useState<SuggestionItem[]>([])
function onQueryChanged({
target: { value: query },
}: ChangeEvent<HTMLInputElement>): void {
const isUrl = query.startsWith('https://') || query.startsWith('http://')
setIsUrl(isUrl)
setProjectUrl(isUrl ? query : undefined)
const onQueryChanged = React.useCallback(
function ({
target: { value: query },
}: ChangeEvent<HTMLInputElement>): void {
const isUrl = query.startsWith('https://') || query.startsWith('http://')
setIsUrl(isUrl)
setProjectUrl(isUrl ? query : undefined)
queryChangedDebounced.current(query)
}
queryChangedDebounced.current(query)
},
[setIsUrl, setProjectUrl, queryChangedDebounced]
)
async function getSuggestions(): Promise<void> {
if (!projectUrl) {
setSuggestions([])
return
}
const getSuggestions = React.useCallback(
async function (): Promise<void> {
if (!projectUrl) {
setSuggestions([])
return
}
setInProgress(true)
setInProgress(true)
const fetch = window.fetch || fetchPonyfill
const res = await fetch(
`${baseUrl}/$suggest/v1?url=${encodeURIComponent(projectUrl)}`
)
let suggestions = [] as SuggestionItem[]
try {
const json = (await res.json()) as SuggestionResponse
// This doesn't validate the response. The default value here prevents
// a crash if the server returns {"err":"Disallowed"}.
suggestions = json.suggestions || []
} catch (e) {
suggestions = []
}
const fetch = window.fetch || fetchPonyfill
const res = await fetch(
`${baseUrl}/$suggest/v1?url=${encodeURIComponent(projectUrl)}`
)
let suggestions = [] as SuggestionItem[]
try {
const json = (await res.json()) as SuggestionResponse
// This doesn't validate the response. The default value here prevents
// a crash if the server returns {"err":"Disallowed"}.
suggestions = json.suggestions || []
} catch (e) {
suggestions = []
}
setInProgress(false)
setSuggestions(suggestions)
}
setInProgress(false)
setSuggestions(suggestions)
},
[setSuggestions, setInProgress, baseUrl, projectUrl]
)
function renderSuggestions(): JSX.Element | null {
if (suggestions.length === 0) {
@@ -105,6 +111,8 @@ export default function SuggestionAndSearch({
)
}
// TODO: Warning: A future version of React will block javascript: URLs as a security precaution
// how else to do this?
return (
<section>
<form action="javascript:void 0" autoComplete="off">

75
package-lock.json generated
View File

@@ -100,7 +100,7 @@
"eslint-plugin-no-extension-in-require": "^0.2.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-sort-class-members": "^1.11.0",
"fetch-ponyfill": "^7.1.0",
@@ -10607,22 +10607,24 @@
}
},
"node_modules/eslint-plugin-react": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz",
"integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==",
"version": "7.26.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz",
"integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.3",
"array.prototype.flatmap": "^1.2.4",
"doctrine": "^2.1.0",
"has": "^1.0.3",
"estraverse": "^5.2.0",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"minimatch": "^3.0.4",
"object.entries": "^1.1.4",
"object.fromentries": "^2.0.4",
"object.hasown": "^1.0.0",
"object.values": "^1.1.4",
"prop-types": "^15.7.2",
"resolve": "^2.0.0-next.3",
"semver": "^6.3.0",
"string.prototype.matchall": "^4.0.5"
},
"engines": {
@@ -10641,6 +10643,15 @@
"node": ">=10"
}
},
"node_modules/eslint-plugin-react/node_modules/estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/eslint-plugin-react/node_modules/resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
@@ -10651,6 +10662,15 @@
"path-parse": "^1.0.6"
}
},
"node_modules/eslint-plugin-react/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/eslint-plugin-sort-class-members": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.11.0.tgz",
@@ -20626,6 +20646,19 @@
"node": ">= 0.4"
}
},
"node_modules/object.hasown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz",
"integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==",
"dev": true,
"dependencies": {
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object.pick": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@@ -38075,25 +38108,33 @@
"requires": {}
},
"eslint-plugin-react": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz",
"integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==",
"version": "7.26.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz",
"integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==",
"dev": true,
"requires": {
"array-includes": "^3.1.3",
"array.prototype.flatmap": "^1.2.4",
"doctrine": "^2.1.0",
"has": "^1.0.3",
"estraverse": "^5.2.0",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"minimatch": "^3.0.4",
"object.entries": "^1.1.4",
"object.fromentries": "^2.0.4",
"object.hasown": "^1.0.0",
"object.values": "^1.1.4",
"prop-types": "^15.7.2",
"resolve": "^2.0.0-next.3",
"semver": "^6.3.0",
"string.prototype.matchall": "^4.0.5"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
},
"resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
@@ -38103,6 +38144,12 @@
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
@@ -45694,6 +45741,16 @@
"has": "^1.0.3"
}
},
"object.hasown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz",
"integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1"
}
},
"object.pick": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",

View File

@@ -187,7 +187,7 @@
"eslint-plugin-no-extension-in-require": "^0.2.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-sort-class-members": "^1.11.0",
"fetch-ponyfill": "^7.1.0",