Replace the debounce-hook with a control

This commit is contained in:
Philipp Stracker 2024-10-24 16:58:03 +02:00
parent f047a9a54f
commit a91b727121
No known key found for this signature in database
3 changed files with 69 additions and 70 deletions

View file

@ -0,0 +1,58 @@
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
/**
* Approach 1: Component Injection
*
* A generic wrapper that adds debounced store updates to any controlled component.
*
* @param {Object} props
* @param {React.ComponentType} props.control The controlled component to render
* @param {string|number} props.value The controlled value
* @param {Function} props.onChange Change handler
* @param {number} [props.delay=300] Debounce delay in milliseconds
*/
const DataStoreControl = ( {
control: ControlComponent,
value: externalValue,
onChange,
delay = 300,
...props
} ) => {
const [ internalValue, setInternalValue ] = useState( externalValue );
const onChangeRef = useRef( onChange );
onChangeRef.current = onChange;
const debouncedUpdate = useRef(
debounce( ( value ) => {
onChangeRef.current( value );
}, delay )
).current;
useEffect( () => {
setInternalValue( externalValue );
debouncedUpdate?.cancel();
}, [ externalValue ] );
useEffect( () => {
return () => debouncedUpdate?.cancel();
}, [ debouncedUpdate ] );
const handleChange = useCallback(
( newValue ) => {
setInternalValue( newValue );
debouncedUpdate( newValue );
},
[ debouncedUpdate ]
);
return (
<ControlComponent
{ ...props }
value={ internalValue }
onChange={ handleChange }
/>
);
};
export default DataStoreControl;

View file

@ -5,7 +5,7 @@ import PaymentMethodIcons from '../../ReusableComponents/PaymentMethodIcons';
import SettingsToggleBlock from '../../ReusableComponents/SettingsToggleBlock';
import Separator from '../../ReusableComponents/Separator';
import { useOnboardingDetails } from '../../../data';
import { useDebounceField } from '../../../utils/hooks';
import DataStoreControl from '../../ReusableComponents/DataStoreControl';
const StepWelcome = () => {
return (
@ -85,16 +85,6 @@ const WelcomeForm = () => {
setClientSecret,
} = useOnboardingDetails();
const [ currentClientId, updateClientId ] = useDebounceField(
setClientId,
clientId
);
const [ currentClientSecret, updateClientSecret ] = useDebounceField(
setClientSecret,
clientSecret
);
const advancedUsersDescription = sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -132,23 +122,25 @@ const WelcomeForm = () => {
isToggled={ !! isManualConnectionMode }
setToggled={ setManualConnectionMode }
>
<TextControl
<DataStoreControl
control={ TextControl }
label={ __(
'Sandbox Client ID',
'woocommerce-paypal-payments'
) }
value={ currentClientId }
onChange={ updateClientId }
></TextControl>
<TextControl
value={ clientId }
onChange={ setClientId }
/>
<DataStoreControl
control={ TextControl }
label={ __(
'Sandbox Secret Key',
'woocommerce-paypal-payments'
) }
value={ currentClientSecret }
onChange={ updateClientSecret }
value={ clientSecret }
onChange={ setClientSecret }
type="password"
></TextControl>
/>
<Button variant="secondary">
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
</Button>

View file

@ -1,51 +0,0 @@
import { useCallback, useEffect, useMemo, useState } from '@wordpress/element';
import { debounce } from '../../../../ppcp-blocks/resources/js/Helper/debounce';
/**
* Hook to manage a form field in a local state, and sync it to the Redux store via a
* debounced setter. This ensures a good balance between fast/lightweight state management
* and a stable Redux integration, even when the value changes very frequently.
*
* @param {Function} syncToStore - Function to sync value to the Redux store.
* @param {string} storeValue - Initial value from the Redux store.
* @param {number} delay - Debounce delay, in milliseconds.
* @return {[string, Function]} Tuple of [fieldValue, updateField]
*/
export const useDebounceField = (
syncToStore,
storeValue = '',
delay = 300
) => {
const [ fieldValue, setFieldValue ] = useState( storeValue );
// Memoize the debounced store sync.
const debouncedSync = useMemo(
() => debounce( syncToStore, delay ),
[ syncToStore, delay ]
);
// Sync field with store changes.
useEffect( () => {
if ( storeValue !== '' && fieldValue !== storeValue ) {
setFieldValue( storeValue );
}
}, [ storeValue, fieldValue ] );
// Handle field updates and store sync.
const updateField = useCallback(
( newValue ) => {
setFieldValue( newValue );
debouncedSync( newValue );
},
[ debouncedSync ]
);
// Cleanup on unmount.
useEffect( () => {
return () => {
debouncedSync?.flush();
};
}, [ debouncedSync ] );
return [ fieldValue, updateField ];
};