From bd1dad01d3f3e9445bd47109b8a45131a20b92a9 Mon Sep 17 00:00:00 2001 From: ugogo Date: Sun, 2 Oct 2022 13:03:08 +0200 Subject: [PATCH] feat: wrap functions in useCallback --- .eslintrc | 6 +- package.json | 1 + src/index.tsx | 337 +++++++++++++++++++++++++++----------------------- yarn.lock | 5 + 4 files changed, 195 insertions(+), 154 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4101259..4598e35 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ "parserOptions": { "project": ["./tsconfig.json", "rollup.config.js"] }, - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "react-hooks"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", @@ -20,6 +20,8 @@ }, "rules": { "react/jsx-sort-props": "error", - "react/sort-prop-types": "error" + "react/sort-prop-types": "error", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" } } diff --git a/package.json b/package.json index 6a69fab..94f291b 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.0.1", "eslint-plugin-react": "^7.31.8", + "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-typescript-sort-keys": "^2.1.0", "npm-run-all": "^4.1.5", "postcss": "^8.4.16", diff --git a/src/index.tsx b/src/index.tsx index e208ed4..30d7de5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,6 +4,7 @@ import React, { createRef, InputHTMLAttributes, KeyboardEvent, + useCallback, useEffect, useMemo, useState, @@ -36,8 +37,11 @@ const ReactInputVerificationCode = ({ * generate a new array, map through it * and replace with the value when possible */ - const fillValues = (value: string) => - new Array(length).fill('').map((_, index) => value[index] ?? ''); + const fillValues = useCallback( + (value: string) => + new Array(length).fill('').map((_, index) => value[index] ?? ''), + [length] + ); const [values, setValues] = useState(fillValues(defaultValue)); const [focusedIndex, setFocusedIndex] = useState(-1); @@ -47,194 +51,223 @@ const ReactInputVerificationCode = ({ [length] ); - const validate = (input: string) => { - if (type === 'number') { - return /^\d/.test(input); - } + const validate = useCallback( + (input: string) => { + if (type === 'number') { + return /^\d/.test(input); + } - if (type === 'alphanumeric') { - return /^[a-zA-Z0-9]/.test(input); - } + if (type === 'alphanumeric') { + return /^[a-zA-Z0-9]/.test(input); + } - return true; - }; + return true; + }, + [type] + ); - const selectInputContent = (index: number) => { - const input = inputsRefs[index].current; + const selectInputContent = useCallback( + (index: number) => { + const input = inputsRefs[index].current; - if (input) { - requestAnimationFrame(() => { - input.select(); - }); - } - }; + if (input) { + requestAnimationFrame(() => { + input.select(); + }); + } + }, + [inputsRefs] + ); - const setValue = (value: string, index: number) => { - const nextValues = [...values]; - nextValues[index] = value; + const setValue = useCallback( + (value: string, index: number) => { + const nextValues = [...values]; + nextValues[index] = value; - setValues(nextValues); + setValues(nextValues); - const stringifiedValues = nextValues.join(''); - const isCompleted = stringifiedValues.length === length; + const stringifiedValues = nextValues.join(''); + const isCompleted = stringifiedValues.length === length; - if (isCompleted) { - onCompleted(stringifiedValues); - return; - } + if (isCompleted) { + onCompleted(stringifiedValues); + return; + } - onChange(stringifiedValues); - }; + onChange(stringifiedValues); + }, + [length, onChange, onCompleted, values] + ); - const focusInput = (index: number) => { - const input = inputsRefs[index]?.current; + const focusInput = useCallback( + (index: number) => { + const input = inputsRefs[index]?.current; - if (input) { - requestAnimationFrame(() => { - input.focus(); - }); - } - }; + if (input) { + requestAnimationFrame(() => { + input.focus(); + }); + } + }, + [inputsRefs] + ); - const blurInput = (index: number) => { - const input = inputsRefs[index]?.current; + const blurInput = useCallback( + (index: number) => { + const input = inputsRefs[index]?.current; - if (input) { - requestAnimationFrame(() => { - input.blur(); - }); - } - }; + if (input) { + requestAnimationFrame(() => { + input.blur(); + }); + } + }, + [inputsRefs] + ); - const onInputFocus = (index: number) => { - const input = inputsRefs[index]?.current; + const onInputFocus = useCallback( + (index: number) => { + const input = inputsRefs[index]?.current; - if (input) { - setFocusedIndex(index); - selectInputContent(index); - } - }; - - const onInputChange = ( - event: ChangeEvent, - index: number - ) => { - const eventValue = event.target.value; - console.log('-------'); - console.log('RIVC:onInputChange', { - event, - eventValue, - focusedIndex, - index, - }); + if (input) { + setFocusedIndex(index); + selectInputContent(index); + } + }, + [inputsRefs, selectInputContent] + ); - /** - * otp code - */ - if (eventValue.length > 1) { - console.log('RIVC: isOtp', true); - console.log('RIVC: fillValues(eventValue)', fillValues(eventValue)); + const onInputChange = useCallback( + (event: ChangeEvent, index: number) => { + const eventValue = event.target.value; + console.log('-------'); + console.log('RIVC:onInputChange', { + event, + eventValue, + focusedIndex, + index, + }); - setValues(fillValues(eventValue)); + /** + * otp code + */ + if (eventValue.length > 1) { + console.log('RIVC: isOtp', true); + console.log('RIVC: fillValues(eventValue)', fillValues(eventValue)); - const isCompleted = eventValue.length === length; - console.log('RIVC: isCompleted', isCompleted); + setValues(fillValues(eventValue)); - if (isCompleted) { - onCompleted(eventValue); - blurInput(index); - return; - } + const isCompleted = eventValue.length === length; + console.log('RIVC: isCompleted', isCompleted); - return; - } + if (isCompleted) { + onCompleted(eventValue); + blurInput(index); + return; + } - console.log('RIVC: isOtp', false); - - /** - * ensure we only display 1 character in the input - * by clearing the already setted value - */ - const value = eventValue.replace(values[index], ''); - - /** - * if the value is not valid, don't go any further - * and select the content of the input for a better UX - */ - if (!validate(value)) { - selectInputContent(index); - return; - } + return; + } - console.log('RIVC', { value }); + console.log('RIVC: isOtp', false); - setValue(value, index); + /** + * ensure we only display 1 character in the input + * by clearing the already setted value + */ + const value = eventValue.replace(values[index], ''); - /** - * if the input is the last of the list - * blur it, otherwise focus the next one - */ - if (index === length - 1) { - blurInput(index); - return; - } + /** + * if the value is not valid, don't go any further + * and select the content of the input for a better UX + */ + if (!validate(value)) { + selectInputContent(index); + return; + } - focusInput(index + 1); - }; + console.log('RIVC', { value }); - const onInputKeyDown = ( - event: KeyboardEvent, - index: number - ) => { - const eventKey = event.key; + setValue(value, index); - if (eventKey === 'Backspace' || eventKey === 'Delete') { /** - * prevent trigger a change event - * `onInputChange` won't be called + * if the input is the last of the list + * blur it, otherwise focus the next one */ - event.preventDefault(); + if (index === length - 1) { + blurInput(index); + return; + } - setValue('', focusedIndex); - focusInput(index - 1); + focusInput(index + 1); + }, + [ + blurInput, + fillValues, + focusInput, + focusedIndex, + length, + onCompleted, + selectInputContent, + setValue, + validate, + values, + ] + ); - return; - } + const onInputKeyDown = useCallback( + (event: KeyboardEvent, index: number) => { + const eventKey = event.key; - /** - * since the value won't change, `onInputChange` won't be called - * only focus the next input - */ - if (eventKey === values[index]) { - focusInput(index + 1); - } - }; + if (eventKey === 'Backspace' || eventKey === 'Delete') { + /** + * prevent trigger a change event + * `onInputChange` won't be called + */ + event.preventDefault(); - const onInputPaste = ( - event: ClipboardEvent, - index: number - ) => { - event.preventDefault(); + setValue('', focusedIndex); + focusInput(index - 1); - const pastedValue = event.clipboardData.getData('text'); - const nextValues = pastedValue.slice(0, length); + return; + } - if (!validate(nextValues)) { - return; - } + /** + * since the value won't change, `onInputChange` won't be called + * only focus the next input + */ + if (eventKey === values[index]) { + focusInput(index + 1); + } + }, + [focusInput, focusedIndex, setValue, values] + ); - setValues(fillValues(nextValues)); + const onInputPaste = useCallback( + (event: ClipboardEvent, index: number) => { + event.preventDefault(); - const isCompleted = nextValues.length === length; + const pastedValue = event.clipboardData.getData('text'); + const nextValues = pastedValue.slice(0, length); - if (isCompleted) { - onCompleted(nextValues); - blurInput(index); - return; - } + if (!validate(nextValues)) { + return; + } + + setValues(fillValues(nextValues)); + + const isCompleted = nextValues.length === length; - focusInput(nextValues.length); - }; + if (isCompleted) { + onCompleted(nextValues); + blurInput(index); + return; + } + + focusInput(nextValues.length); + }, + [blurInput, fillValues, focusInput, length, onCompleted, validate] + ); /** * autoFocus @@ -243,7 +276,7 @@ const ReactInputVerificationCode = ({ if (autoFocus) { focusInput(0); } - }, [inputsRefs]); + }, [autoFocus, focusInput]); return (
diff --git a/yarn.lock b/yarn.lock index 27188bd..916b894 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5688,6 +5688,11 @@ eslint-plugin-promise@^6.0.1: resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz#a8cddf96a67c4059bdabf4d724a29572188ae423" integrity sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw== +eslint-plugin-react-hooks@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + eslint-plugin-react@^7.31.8: version "7.31.8" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz#3a4f80c10be1bcbc8197be9e8b641b2a3ef219bf"