Skip to content

Commit b9ba60d

Browse files
committed
Wrapping event handlers in useCallback
1 parent 6603e96 commit b9ba60d

File tree

7 files changed

+119
-80
lines changed

7 files changed

+119
-80
lines changed

packages/core/src/components/fields/NumberField.tsx

+21-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useCallback } from "react";
22
import { asNumber, FieldProps } from "@rjsf/utils";
33

44
// Matches a string that ends in a . character, optionally followed by a sequence of
@@ -41,26 +41,29 @@ function NumberField<T = any, F = any>(props: FieldProps<T, F>) {
4141
*
4242
* @param value - The current value for the change occurring
4343
*/
44-
const handleChange = (value: FieldProps<T, F>["value"]) => {
45-
// Cache the original value in component state
46-
setLastValue(value);
44+
const handleChange = useCallback(
45+
(value: FieldProps<T, F>["value"]) => {
46+
// Cache the original value in component state
47+
setLastValue(value);
4748

48-
// Normalize decimals that don't start with a zero character in advance so
49-
// that the rest of the normalization logic is simpler
50-
if (`${value}`.charAt(0) === ".") {
51-
value = `0${value}`;
52-
}
49+
// Normalize decimals that don't start with a zero character in advance so
50+
// that the rest of the normalization logic is simpler
51+
if (`${value}`.charAt(0) === ".") {
52+
value = `0${value}`;
53+
}
5354

54-
// Check that the value is a string (this can happen if the widget used is a
55-
// <select>, due to an enum declaration etc) then, if the value ends in a
56-
// trailing decimal point or multiple zeroes, strip the trailing values
57-
const processed =
58-
typeof value === "string" && value.match(trailingCharMatcherWithPrefix)
59-
? asNumber(value.replace(trailingCharMatcher, ""))
60-
: asNumber(value);
55+
// Check that the value is a string (this can happen if the widget used is a
56+
// <select>, due to an enum declaration etc) then, if the value ends in a
57+
// trailing decimal point or multiple zeroes, strip the trailing values
58+
const processed =
59+
typeof value === "string" && value.match(trailingCharMatcherWithPrefix)
60+
? asNumber(value.replace(trailingCharMatcher, ""))
61+
: asNumber(value);
6162

62-
onChange(processed as unknown as T);
63-
};
63+
onChange(processed as unknown as T);
64+
},
65+
[onChange]
66+
);
6467

6568
if (typeof lastValue === "string" && typeof value === "number") {
6669
// Construct a regular expression that checks for a string that consists

packages/core/src/components/widgets/AltDateWidget.tsx

+29-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { MouseEvent, useEffect, useReducer } from "react";
1+
import React, { MouseEvent, useCallback, useEffect, useReducer } from "react";
22

33
import {
44
parseDateString,
@@ -132,27 +132,36 @@ function AltDateWidget<T = any, F = any>({
132132
}
133133
}, [state, time, onChange]);
134134

135-
const handleChange = (property: keyof DateObject, value: string) => {
136-
setState({ [property]: value });
137-
};
135+
const handleChange = useCallback(
136+
(property: keyof DateObject, value: string) => {
137+
setState({ [property]: value });
138+
},
139+
[]
140+
);
138141

139-
const handleSetNow = (event: MouseEvent<HTMLAnchorElement>) => {
140-
event.preventDefault();
141-
if (disabled || readonly) {
142-
return;
143-
}
144-
const nowDateObj = parseDateString(new Date().toJSON(), time);
145-
setState(nowDateObj);
146-
};
142+
const handleSetNow = useCallback(
143+
(event: MouseEvent<HTMLAnchorElement>) => {
144+
event.preventDefault();
145+
if (disabled || readonly) {
146+
return;
147+
}
148+
const nowDateObj = parseDateString(new Date().toJSON(), time);
149+
setState(nowDateObj);
150+
},
151+
[disabled, readonly, time]
152+
);
147153

148-
const handleClear = (event: MouseEvent<HTMLAnchorElement>) => {
149-
event.preventDefault();
150-
if (disabled || readonly) {
151-
return;
152-
}
153-
setState(parseDateString("", time));
154-
onChange(undefined);
155-
};
154+
const handleClear = useCallback(
155+
(event: MouseEvent<HTMLAnchorElement>) => {
156+
event.preventDefault();
157+
if (disabled || readonly) {
158+
return;
159+
}
160+
setState(parseDateString("", time));
161+
onChange(undefined);
162+
},
163+
[disabled, readonly, time, onChange]
164+
);
156165

157166
return (
158167
<ul className="list-inline">

packages/core/src/components/widgets/CheckboxWidget.tsx

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
22
import { getTemplate, schemaRequiresTrueValue, WidgetProps } from "@rjsf/utils";
33

44
/** The `CheckBoxWidget` is a widget for rendering boolean properties.
@@ -30,14 +30,23 @@ function CheckboxWidget<T = any, F = any>({
3030
// "const" or "enum" keywords
3131
const required = schemaRequiresTrueValue(schema);
3232

33-
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) =>
34-
onChange(event.target.checked);
33+
const handleChange = useCallback(
34+
(event: React.ChangeEvent<HTMLInputElement>) =>
35+
onChange(event.target.checked),
36+
[onChange]
37+
);
3538

36-
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) =>
37-
onBlur(id, event.target.checked);
39+
const handleBlur = useCallback(
40+
(event: React.FocusEvent<HTMLInputElement>) =>
41+
onBlur(id, event.target.checked),
42+
[]
43+
);
3844

39-
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) =>
40-
onFocus(id, event.target.checked);
45+
const handleFocus = useCallback(
46+
(event: React.FocusEvent<HTMLInputElement>) =>
47+
onFocus(id, event.target.checked),
48+
[onFocus, id]
49+
);
4150

4251
return (
4352
<div className={`checkbox ${disabled || readonly ? "disabled" : ""}`}>
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
22
import { getTemplate, WidgetProps } from "@rjsf/utils";
33

44
/** The `DateWidget` component uses the `BaseInputTemplate` changing the type to `date` and transforms
@@ -13,11 +13,10 @@ export default function DateWidget<T = any, F = any>(props: WidgetProps<T, F>) {
1313
registry,
1414
options
1515
);
16-
return (
17-
<BaseInputTemplate
18-
type="date"
19-
{...props}
20-
onChange={(value) => onChange(value || undefined)}
21-
/>
16+
const handleChange = useCallback(
17+
(value: React.ChangeEvent) => onChange(value || undefined),
18+
[onChange]
2219
);
20+
21+
return <BaseInputTemplate type="date" {...props} onChange={handleChange} />;
2322
}

packages/core/src/components/widgets/RadioWidget.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FocusEvent } from "react";
1+
import React, { FocusEvent, useCallback } from "react";
22
import { WidgetProps } from "@rjsf/utils";
33

44
/** The `RadioWidget` is a widget for rendering a radio group.
@@ -24,11 +24,15 @@ function RadioWidget<T = any, F = any>({
2424
// checked={checked} has been moved above name={name}, As mentioned in #349;
2525
// this is a temporary fix for radio button rendering bug in React, facebook/react#7630.
2626

27-
const handleBlur = (event: FocusEvent<HTMLInputElement>) =>
28-
onBlur(id, event.target.value);
27+
const handleBlur = useCallback(
28+
(event: FocusEvent<HTMLInputElement>) => onBlur(id, event.target.value),
29+
[onBlur, id]
30+
);
2931

30-
const handleFocus = (event: FocusEvent<HTMLInputElement>) =>
31-
onFocus(id, event.target.value);
32+
const handleFocus = useCallback(
33+
(event: FocusEvent<HTMLInputElement>) => onFocus(id, event.target.value),
34+
[onFocus, id]
35+
);
3236

3337
return (
3438
<div className="field-radio-group" id={id}>

packages/core/src/components/widgets/SelectWidget.tsx

+22-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ChangeEvent, FocusEvent } from "react";
1+
import React, { ChangeEvent, FocusEvent, useCallback } from "react";
22
import { processSelectValue, WidgetProps } from "@rjsf/utils";
33

44
function getValue(
@@ -37,20 +37,29 @@ function SelectWidget<T = any, F = any>({
3737
const { enumOptions, enumDisabled } = options;
3838
const emptyValue = multiple ? [] : "";
3939

40-
const handleFocus = (event: FocusEvent<HTMLSelectElement>) => {
41-
const newValue = getValue(event, multiple);
42-
return onFocus(id, processSelectValue(schema, newValue, options));
43-
};
40+
const handleFocus = useCallback(
41+
(event: FocusEvent<HTMLSelectElement>) => {
42+
const newValue = getValue(event, multiple);
43+
return onFocus(id, processSelectValue(schema, newValue, options));
44+
},
45+
[onFocus, id, schema, multiple, options]
46+
);
4447

45-
const handleBlur = (event: FocusEvent<HTMLSelectElement>) => {
46-
const newValue = getValue(event, multiple);
47-
return onBlur(id, processSelectValue(schema, newValue, options));
48-
};
48+
const handleBlur = useCallback(
49+
(event: FocusEvent<HTMLSelectElement>) => {
50+
const newValue = getValue(event, multiple);
51+
return onBlur(id, processSelectValue(schema, newValue, options));
52+
},
53+
[onBlur, id, schema, multiple, options]
54+
);
4955

50-
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
51-
const newValue = getValue(event, multiple);
52-
return onChange(processSelectValue(schema, newValue, options));
53-
};
56+
const handleChange = useCallback(
57+
(event: ChangeEvent<HTMLSelectElement>) => {
58+
const newValue = getValue(event, multiple);
59+
return onChange(processSelectValue(schema, newValue, options));
60+
},
61+
[onChange, schema, multiple, options]
62+
);
5463

5564
return (
5665
<select

packages/core/src/components/widgets/TextareaWidget.tsx

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FocusEvent } from "react";
1+
import React, { FocusEvent, useCallback } from "react";
22
import { WidgetProps } from "@rjsf/utils";
33

44
/** The `TextareaWidget` is a widget for rendering input fields as textarea.
@@ -18,17 +18,23 @@ function TextareaWidget<T = any, F = any>({
1818
onBlur,
1919
onFocus,
2020
}: WidgetProps<T, F>) {
21-
const handleChange = ({
22-
target: { value },
23-
}: React.ChangeEvent<HTMLTextAreaElement>) =>
24-
onChange(value === "" ? options.emptyValue : value);
21+
const handleChange = useCallback(
22+
({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>) =>
23+
onChange(value === "" ? options.emptyValue : value),
24+
[onChange, options.emptyValue]
25+
);
2526

26-
const handleBlur = ({ target: { value } }: FocusEvent<HTMLTextAreaElement>) =>
27-
onBlur(id, value);
27+
const handleBlur = useCallback(
28+
({ target: { value } }: FocusEvent<HTMLTextAreaElement>) =>
29+
onBlur(id, value),
30+
[onBlur, id]
31+
);
2832

29-
const handleFocus = ({
30-
target: { value },
31-
}: FocusEvent<HTMLTextAreaElement>) => onFocus(id, value);
33+
const handleFocus = useCallback(
34+
({ target: { value } }: FocusEvent<HTMLTextAreaElement>) =>
35+
onFocus(id, value),
36+
[id, onFocus]
37+
);
3238

3339
return (
3440
<textarea

0 commit comments

Comments
 (0)