From b9ba60d287174b3912ad62547366f976af7a93de Mon Sep 17 00:00:00 2001 From: Jimmy Callin Date: Mon, 22 Aug 2022 21:26:53 +0200 Subject: [PATCH 1/3] Wrapping event handlers in useCallback --- .../src/components/fields/NumberField.tsx | 39 ++++++++------- .../src/components/widgets/AltDateWidget.tsx | 49 +++++++++++-------- .../src/components/widgets/CheckboxWidget.tsx | 23 ++++++--- .../src/components/widgets/DateWidget.tsx | 13 +++-- .../src/components/widgets/RadioWidget.tsx | 14 ++++-- .../src/components/widgets/SelectWidget.tsx | 35 ++++++++----- .../src/components/widgets/TextareaWidget.tsx | 26 ++++++---- 7 files changed, 119 insertions(+), 80 deletions(-) diff --git a/packages/core/src/components/fields/NumberField.tsx b/packages/core/src/components/fields/NumberField.tsx index 3914dd480a..6ab99f0fe0 100644 --- a/packages/core/src/components/fields/NumberField.tsx +++ b/packages/core/src/components/fields/NumberField.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useCallback } from "react"; import { asNumber, FieldProps } from "@rjsf/utils"; // Matches a string that ends in a . character, optionally followed by a sequence of @@ -41,26 +41,29 @@ function NumberField(props: FieldProps) { * * @param value - The current value for the change occurring */ - const handleChange = (value: FieldProps["value"]) => { - // Cache the original value in component state - setLastValue(value); + const handleChange = useCallback( + (value: FieldProps["value"]) => { + // Cache the original value in component state + setLastValue(value); - // Normalize decimals that don't start with a zero character in advance so - // that the rest of the normalization logic is simpler - if (`${value}`.charAt(0) === ".") { - value = `0${value}`; - } + // Normalize decimals that don't start with a zero character in advance so + // that the rest of the normalization logic is simpler + if (`${value}`.charAt(0) === ".") { + value = `0${value}`; + } - // Check that the value is a string (this can happen if the widget used is a - // , due to an enum declaration etc) then, if the value ends in a + // trailing decimal point or multiple zeroes, strip the trailing values + const processed = + typeof value === "string" && value.match(trailingCharMatcherWithPrefix) + ? asNumber(value.replace(trailingCharMatcher, "")) + : asNumber(value); - onChange(processed as unknown as T); - }; + onChange(processed as unknown as T); + }, + [onChange] + ); if (typeof lastValue === "string" && typeof value === "number") { // Construct a regular expression that checks for a string that consists diff --git a/packages/core/src/components/widgets/AltDateWidget.tsx b/packages/core/src/components/widgets/AltDateWidget.tsx index f994b8fb51..9f6c6b6b3c 100644 --- a/packages/core/src/components/widgets/AltDateWidget.tsx +++ b/packages/core/src/components/widgets/AltDateWidget.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent, useEffect, useReducer } from "react"; +import React, { MouseEvent, useCallback, useEffect, useReducer } from "react"; import { parseDateString, @@ -132,27 +132,36 @@ function AltDateWidget({ } }, [state, time, onChange]); - const handleChange = (property: keyof DateObject, value: string) => { - setState({ [property]: value }); - }; + const handleChange = useCallback( + (property: keyof DateObject, value: string) => { + setState({ [property]: value }); + }, + [] + ); - const handleSetNow = (event: MouseEvent) => { - event.preventDefault(); - if (disabled || readonly) { - return; - } - const nowDateObj = parseDateString(new Date().toJSON(), time); - setState(nowDateObj); - }; + const handleSetNow = useCallback( + (event: MouseEvent) => { + event.preventDefault(); + if (disabled || readonly) { + return; + } + const nowDateObj = parseDateString(new Date().toJSON(), time); + setState(nowDateObj); + }, + [disabled, readonly, time] + ); - const handleClear = (event: MouseEvent) => { - event.preventDefault(); - if (disabled || readonly) { - return; - } - setState(parseDateString("", time)); - onChange(undefined); - }; + const handleClear = useCallback( + (event: MouseEvent) => { + event.preventDefault(); + if (disabled || readonly) { + return; + } + setState(parseDateString("", time)); + onChange(undefined); + }, + [disabled, readonly, time, onChange] + ); return (
    diff --git a/packages/core/src/components/widgets/CheckboxWidget.tsx b/packages/core/src/components/widgets/CheckboxWidget.tsx index 78c32cabef..978816d205 100644 --- a/packages/core/src/components/widgets/CheckboxWidget.tsx +++ b/packages/core/src/components/widgets/CheckboxWidget.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { getTemplate, schemaRequiresTrueValue, WidgetProps } from "@rjsf/utils"; /** The `CheckBoxWidget` is a widget for rendering boolean properties. @@ -30,14 +30,23 @@ function CheckboxWidget({ // "const" or "enum" keywords const required = schemaRequiresTrueValue(schema); - const handleChange = (event: React.ChangeEvent) => - onChange(event.target.checked); + const handleChange = useCallback( + (event: React.ChangeEvent) => + onChange(event.target.checked), + [onChange] + ); - const handleBlur = (event: React.FocusEvent) => - onBlur(id, event.target.checked); + const handleBlur = useCallback( + (event: React.FocusEvent) => + onBlur(id, event.target.checked), + [] + ); - const handleFocus = (event: React.FocusEvent) => - onFocus(id, event.target.checked); + const handleFocus = useCallback( + (event: React.FocusEvent) => + onFocus(id, event.target.checked), + [onFocus, id] + ); return (
    diff --git a/packages/core/src/components/widgets/DateWidget.tsx b/packages/core/src/components/widgets/DateWidget.tsx index 59100fe20c..75799e520c 100644 --- a/packages/core/src/components/widgets/DateWidget.tsx +++ b/packages/core/src/components/widgets/DateWidget.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { getTemplate, WidgetProps } from "@rjsf/utils"; /** The `DateWidget` component uses the `BaseInputTemplate` changing the type to `date` and transforms @@ -13,11 +13,10 @@ export default function DateWidget(props: WidgetProps) { registry, options ); - return ( - onChange(value || undefined)} - /> + const handleChange = useCallback( + (value: React.ChangeEvent) => onChange(value || undefined), + [onChange] ); + + return ; } diff --git a/packages/core/src/components/widgets/RadioWidget.tsx b/packages/core/src/components/widgets/RadioWidget.tsx index a86c100904..cf4ed51180 100644 --- a/packages/core/src/components/widgets/RadioWidget.tsx +++ b/packages/core/src/components/widgets/RadioWidget.tsx @@ -1,4 +1,4 @@ -import React, { FocusEvent } from "react"; +import React, { FocusEvent, useCallback } from "react"; import { WidgetProps } from "@rjsf/utils"; /** The `RadioWidget` is a widget for rendering a radio group. @@ -24,11 +24,15 @@ function RadioWidget({ // checked={checked} has been moved above name={name}, As mentioned in #349; // this is a temporary fix for radio button rendering bug in React, facebook/react#7630. - const handleBlur = (event: FocusEvent) => - onBlur(id, event.target.value); + const handleBlur = useCallback( + (event: FocusEvent) => onBlur(id, event.target.value), + [onBlur, id] + ); - const handleFocus = (event: FocusEvent) => - onFocus(id, event.target.value); + const handleFocus = useCallback( + (event: FocusEvent) => onFocus(id, event.target.value), + [onFocus, id] + ); return (
    diff --git a/packages/core/src/components/widgets/SelectWidget.tsx b/packages/core/src/components/widgets/SelectWidget.tsx index f505e56dde..80c8716e91 100644 --- a/packages/core/src/components/widgets/SelectWidget.tsx +++ b/packages/core/src/components/widgets/SelectWidget.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, FocusEvent } from "react"; +import React, { ChangeEvent, FocusEvent, useCallback } from "react"; import { processSelectValue, WidgetProps } from "@rjsf/utils"; function getValue( @@ -37,20 +37,29 @@ function SelectWidget({ const { enumOptions, enumDisabled } = options; const emptyValue = multiple ? [] : ""; - const handleFocus = (event: FocusEvent) => { - const newValue = getValue(event, multiple); - return onFocus(id, processSelectValue(schema, newValue, options)); - }; + const handleFocus = useCallback( + (event: FocusEvent) => { + const newValue = getValue(event, multiple); + return onFocus(id, processSelectValue(schema, newValue, options)); + }, + [onFocus, id, schema, multiple, options] + ); - const handleBlur = (event: FocusEvent) => { - const newValue = getValue(event, multiple); - return onBlur(id, processSelectValue(schema, newValue, options)); - }; + const handleBlur = useCallback( + (event: FocusEvent) => { + const newValue = getValue(event, multiple); + return onBlur(id, processSelectValue(schema, newValue, options)); + }, + [onBlur, id, schema, multiple, options] + ); - const handleChange = (event: ChangeEvent) => { - const newValue = getValue(event, multiple); - return onChange(processSelectValue(schema, newValue, options)); - }; + const handleChange = useCallback( + (event: ChangeEvent) => { + const newValue = getValue(event, multiple); + return onChange(processSelectValue(schema, newValue, options)); + }, + [onChange, schema, multiple, options] + ); return (