diff --git a/src/Utils/.Notifications.js.swp b/src/Utils/.Notifications.js.swp deleted file mode 100644 index 322de92b8df..00000000000 Binary files a/src/Utils/.Notifications.js.swp and /dev/null differ diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index ca47c02803e..9a9329941ad 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,7 +1,7 @@ import { Stack, alert, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { camelCase, startCase } from "./utils"; +import { camelCase, startCase } from "@/Utils/utils"; defaultModules.set(PNotifyMobile, {}); diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index baff0d12e94..941a4ca5a4f 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -1,5 +1,3 @@ -import { useEffect, useRef } from "react"; - import { PatientModel } from "@/components/Patient/models"; import { AREACODES, IN_LANDLINE_AREA_CODES } from "@/common/constants"; @@ -569,23 +567,35 @@ export const camelCase = (str: string) => { .replace(/^[A-Z]/, (c) => c.toLowerCase()); }; -export const useDebounce = ( - callback: (...args: string[]) => void, - delay: number, -) => { - const callbackRef = useRef(callback); - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - - const timeoutRef = useRef | null>(null); - const debouncedCallback = (...args: string[]) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); +export function setNestedValueSafely( + obj: Record, + path: string, + value: any, +) { + const keys = path.split("."); + let current = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + + // Protect against prototype pollution by skipping unsafe keys + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; } - timeoutRef.current = setTimeout(() => { - callbackRef.current(...args); - }, delay); - }; - return debouncedCallback; -}; + + // Use Object.create(null) to prevent accidental inheritance from Object prototype + current[key] = current[key] || Object.create(null); + current = current[key]; + } + + const lastKey = keys[keys.length - 1]; + + // Final key assignment, ensuring no prototype pollution vulnerability + if ( + lastKey !== "__proto__" && + lastKey !== "constructor" && + lastKey !== "prototype" + ) { + current[lastKey] = value; + } +} diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index 0e298377002..15c676220fd 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -9,6 +9,7 @@ import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; +import { setNestedValueSafely } from "@/Utils/utils"; const initialState = { changedFields: {}, @@ -91,31 +92,8 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - const keys = name.split("."); - let current = changedFields; - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - - // Protect against prototype pollution by skipping unsafe keys - crai - if (key === "__proto__" || key === "constructor" || key === "prototype") { - continue; - } - - // Use Object.create(null) to prevent accidental inheritance from Object prototype - coderabbit - current[key] = current[key] || Object.create(null); - current = current[key]; - } - const lastKey = keys[keys.length - 1]; - - // Final key assignment, ensuring no prototype pollution vulnerability - coderabbit - if ( - lastKey !== "__proto__" && - lastKey !== "constructor" && - lastKey !== "prototype" - ) { - current[lastKey] = value; - } + setNestedValueSafely(changedFields, name, value); dispatch({ type: "set_changed_fields", changedFields }); }; diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx index 31cbce47d7e..e0d8c543ce9 100644 --- a/src/components/Patient/DiagnosesFilter.tsx +++ b/src/components/Patient/DiagnosesFilter.tsx @@ -5,10 +5,12 @@ import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/AutocompleteMultiselect"; +import useDebounce from "@/hooks/useDebounce"; + import { Error } from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; -import { mergeQueryOptions, useDebounce } from "@/Utils/utils"; +import { mergeQueryOptions } from "@/Utils/utils"; export const FILTER_BY_DIAGNOSES_KEYS = [ "diagnoses", @@ -70,7 +72,7 @@ export default function DiagnosesFilter(props: Props) { const debouncedQuery = useDebounce((query: string) => { refetch({ query: { query } }); - }, 0); + }, 300); return ( { } } } - }, 0); + }, 300); const handleDialogClose = (action: string) => { if (action === "transfer") { diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 00000000000..961a976d893 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef } from "react"; + +export default function useDebounce( + callback: (...args: string[]) => void, + delay: number, +) { + const callbackRef = useRef(callback); + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const timeoutRef = useRef | null>(null); + const debouncedCallback = (...args: string[]) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + callbackRef.current(...args); + }, delay); + }; + return debouncedCallback; +}