From ecffbb8e72d7698f0cbad6e9215bd34780855253 Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sat, 5 Oct 2024 21:54:36 +0700 Subject: [PATCH 01/11] Ngoc Nguyen 3 --- src/App.tsx | 4 +++ src/components/Input/index.tsx | 50 +++++++++++++++++++++++++++++++-- src/components/Input/input.scss | 37 ++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index afe48ac..229125a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import reactLogo from './assets/react.svg' import viteLogo from '/vite.svg' import './App.css' +import Input from './components/Input' function App() { const [count, setCount] = useState(0) @@ -17,6 +18,9 @@ function App() {

Vite + React

+
-

- Edit src/App.tsx and save to test HMR -

-
+ + +

Click on the Vite and React logos to learn more

diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 8d32f31..3d254d4 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,7 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; -import { useState, useRef } from "react"; +import { forwardRef, useRef, useImperativeHandle, useState } from "react"; import React from "react"; export interface InputProps { @@ -12,56 +12,71 @@ export interface InputProps { onSelectItem: (item: string) => void; } -interface AutocompleteResultsProps { - names: string[]; - onSelectItem: (item: string) => void; -} +const AutocompleteResults = forwardRef((props, ref) => { + console.log("xxxx"); + + const [isLoading, setIsLoading] = useState(false); + const [errorMsg, setErrorMsg] = useState(""); + const [names, setNames] = useState([]); + const [keywords, setKeywords] = useState(""); + + const onChangeHandler = debounce((evt : React.ChangeEvent) => { + const value = evt.target.value; + setIsLoading(true); + setErrorMsg(""); + + fetchData(value).then(results => { + setNames(results || []); + setKeywords(value); + console.log(results); + }).catch(e =>{ + setNames([]); + setErrorMsg(e as string); + }).finally(() => { + setIsLoading(false); + }); + }, 100); + + useImperativeHandle(ref, () => ({ + onChangeHandler + })); -const AutocompleteResults: React.FC = ({ names, onSelectItem }) => { return ( -
    - {names.map((resultElm) => ( -
  • onSelectItem(resultElm)}> - {resultElm} -
  • - ))} -
+ <> + {isLoading && } + {keywords.length ? + ( +
    + {errorMsg ? (
  • {errorMsg}
  • ) : ""} + {(!errorMsg && names.length == 0) ? (
  • No results!
  • ) : ""} + {!errorMsg ? names.map((resultElm) => ( +
  • props.onSelectItem(resultElm)}> + {resultElm} +
  • + )) : ""} +
+ ) + : "" + } + ); -}; - +}); -const Input = React.memo(({ placeholder, onSelectItem }: InputProps) => { +const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log - console.log('input re-render') - const [isLoading, setIsLoading] = useState(false); - const namesRef = useRef([]); - - const onChangeHandler = useRef(debounce(async (evt : React.ChangeEvent) => { - const keywords = evt.target.value; - setIsLoading(true); - const results = await fetchData(keywords); - namesRef.current = results || []; - setIsLoading(false); - console.log(results); - }, 100)).current; + console.log('input re-render'); + const childRef = useRef(); // Your code start here return (
- {isLoading && ( - - )} - - - { - namesRef.current.length && - - } + childRef.current.onChangeHandler(e)}> +
) // Your code end here -}); +}; export default Input; diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 16ed7c4..aa03c16 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -26,9 +26,12 @@ body { font-size: 1.6rem; } max-height: 32rem; } + &-msg, &-item { line-height: 4.8rem; padding: 0 3.2rem; + } + &-item { cursor: pointer; &:hover { background: #333; From ad62864606b2ee8e84cbb65a2962a27d6d8e99de Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sun, 6 Oct 2024 03:09:13 +0700 Subject: [PATCH 03/11] Prevent re-commit - ngoc.nguyen3 --- src/components/Input/index.tsx | 50 ++++++++++++++++----------------- src/components/Input/input.scss | 12 ++++++++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 3d254d4..f0cb211 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,7 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; -import { forwardRef, useRef, useImperativeHandle, useState } from "react"; +import { forwardRef, useRef, useImperativeHandle, useState, useCallback, useMemo } from "react"; import React from "react"; export interface InputProps { @@ -16,26 +16,29 @@ const AutocompleteResults = forwardRef((props, ref) => { console.log("xxxx"); const [isLoading, setIsLoading] = useState(false); - const [errorMsg, setErrorMsg] = useState(""); - const [names, setNames] = useState([]); - const [keywords, setKeywords] = useState(""); + // const isLoadingRef = useRef(false); + const errorMsgRef = useRef(""); + const namesRef = useRef([]); + const keywordsRef = useRef(""); - const onChangeHandler = debounce((evt : React.ChangeEvent) => { + + //Memoized the onChangeHandler + const onChangeHandler = useCallback(debounce((evt : React.ChangeEvent) => { const value = evt.target.value; + errorMsgRef.current = ""; setIsLoading(true); - setErrorMsg(""); fetchData(value).then(results => { - setNames(results || []); - setKeywords(value); + namesRef.current = (results || []); + keywordsRef.current = value; console.log(results); }).catch(e =>{ - setNames([]); - setErrorMsg(e as string); + namesRef.current = []; + errorMsgRef.current = e; }).finally(() => { setIsLoading(false); }); - }, 100); + }, 100), [keywordsRef.current]); useImperativeHandle(ref, () => ({ onChangeHandler @@ -43,20 +46,17 @@ const AutocompleteResults = forwardRef((props, ref) => { return ( <> - {isLoading && } - {keywords.length ? - ( -
    - {errorMsg ? (
  • {errorMsg}
  • ) : ""} - {(!errorMsg && names.length == 0) ? (
  • No results!
  • ) : ""} - {!errorMsg ? names.map((resultElm) => ( -
  • props.onSelectItem(resultElm)}> - {resultElm} -
  • - )) : ""} -
- ) - : "" + {
} + { +
    + {errorMsgRef.current ? (
  • {errorMsgRef.current}
  • ) : ""} + {(!errorMsgRef.current && namesRef.current.length == 0) ? (
  • No results!
  • ) : ""} + {!errorMsgRef.current ? namesRef.current.map((resultElm) => ( +
  • props.onSelectItem(resultElm)}> + {resultElm} +
  • + )) : ""} +
} ); diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index aa03c16..d07422d 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -24,6 +24,10 @@ body { font-size: 1.6rem; } padding: 1.6rem 0; overflow: auto; max-height: 32rem; + display: none; + &.show { + display: block; + } } &-msg, @@ -37,4 +41,12 @@ body { font-size: 1.6rem; } background: #333; } } +} + + +.loader-area { + display: none; + &.show { + display: block; + } } \ No newline at end of file From 7f453acb55363e720add2a501fe53d3fd237662d Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sun, 6 Oct 2024 03:23:42 +0700 Subject: [PATCH 04/11] Prevent re-commit - ngoc.nguyen3 --- src/components/Input/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index f0cb211..0f2625a 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,7 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; -import { forwardRef, useRef, useImperativeHandle, useState, useCallback, useMemo } from "react"; +import { forwardRef, useRef, useImperativeHandle, useState, useCallback } from "react"; import React from "react"; export interface InputProps { @@ -12,7 +12,11 @@ export interface InputProps { onSelectItem: (item: string) => void; } -const AutocompleteResults = forwardRef((props, ref) => { +export interface IAutocompleteResultsProps { + onSelectItem: (item: string) => void +} + +const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) => { console.log("xxxx"); const [isLoading, setIsLoading] = useState(false); @@ -66,7 +70,7 @@ const AutocompleteResults = forwardRef((props, ref) => { const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log console.log('input re-render'); - const childRef = useRef(); + const childRef = useRef(); // Your code start here return ( From e796b09ab573328690286a01d1d23a58dba18c48 Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sun, 6 Oct 2024 03:32:19 +0700 Subject: [PATCH 05/11] add comments to code - ngoc.nguyen3 --- src/components/Input/index.tsx | 4 ++++ src/utils/deboucne.ts | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 0f2625a..3927f66 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -21,6 +21,8 @@ const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) const [isLoading, setIsLoading] = useState(false); // const isLoadingRef = useRef(false); + + //Use useRef instead of useState for preventing create new variables const errorMsgRef = useRef(""); const namesRef = useRef([]); const keywordsRef = useRef(""); @@ -70,6 +72,8 @@ const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log console.log('input re-render'); + + //use childRef to prevent re-render the Input component, only re-render the AutoComplete and Loader component const childRef = useRef(); // Your code start here diff --git a/src/utils/deboucne.ts b/src/utils/deboucne.ts index 47a2c01..2025915 100644 --- a/src/utils/deboucne.ts +++ b/src/utils/deboucne.ts @@ -1,10 +1,15 @@ +const timerList : any = []; +//Wrong debounce code! has to clear All timers before make a new timer, instead of make a new Timer every call debounce() times export const debounce = (callback: Function, wait: number) => { let timeoutId: number = 0; return (...args: unknown[]) => { - window.clearTimeout(timeoutId); + timerList.forEach((timerItem : any)=> { + window.clearTimeout(timerItem); + }) timeoutId = window.setTimeout(() => { callback(...args); }, wait); + timerList.push(timeoutId); }; } \ No newline at end of file From 8b72ba0a1c828028630bc36c15f35a461ea4af38 Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sun, 6 Oct 2024 04:04:21 +0700 Subject: [PATCH 06/11] add comments to code - ngoc.nguyen3 --- src/components/Input/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 3927f66..d769b21 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -37,14 +37,14 @@ const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) fetchData(value).then(results => { namesRef.current = (results || []); keywordsRef.current = value; - console.log(results); + console.log(value, results); }).catch(e =>{ namesRef.current = []; errorMsgRef.current = e; }).finally(() => { setIsLoading(false); }); - }, 100), [keywordsRef.current]); + }, 500), [keywordsRef.current]); useImperativeHandle(ref, () => ({ onChangeHandler From 4ed83336b5cb9ddd362d4668d48bc79b88e7cee3 Mon Sep 17 00:00:00 2001 From: Nguyen Chung Hieu Date: Sun, 6 Oct 2024 04:14:33 +0700 Subject: [PATCH 07/11] add comments to code - ngoc.nguyen3 --- src/components/Input/index.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index d769b21..486d764 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -19,10 +19,10 @@ export interface IAutocompleteResultsProps { const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) => { console.log("xxxx"); - const [isLoading, setIsLoading] = useState(false); - // const isLoadingRef = useRef(false); - + const [hasChange, setChange] = useState(0); + const [isLoading, setLoading] = useState(false); //Use useRef instead of useState for preventing create new variables + const isLoadingRef = useRef(false); const errorMsgRef = useRef(""); const namesRef = useRef([]); const keywordsRef = useRef(""); @@ -32,19 +32,20 @@ const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) const onChangeHandler = useCallback(debounce((evt : React.ChangeEvent) => { const value = evt.target.value; errorMsgRef.current = ""; - setIsLoading(true); + setLoading(true); fetchData(value).then(results => { namesRef.current = (results || []); keywordsRef.current = value; + setChange(new Date().getTime()); console.log(value, results); }).catch(e =>{ namesRef.current = []; errorMsgRef.current = e; }).finally(() => { - setIsLoading(false); + setLoading(false); }); - }, 500), [keywordsRef.current]); + }, 500), [hasChange]); useImperativeHandle(ref, () => ({ onChangeHandler @@ -54,10 +55,10 @@ const AutocompleteResults = forwardRef((props : IAutocompleteResultsProps, ref) <> {
} { -