اگر آپ نے React ایکو سسٹم میں کافی وقت گزارا ہے، تو آپ نے شاید کوڈ بیسز دیکھے ہوں گے جہاں تقریباً ہر فنکشن کو اس طرح لپیٹ دیا گیا ہے: useCallback حسابی قدر اس طرح لپیٹی جاتی ہے: useMemo.
وجہ یہ ہے کہ "نوٹ لینے سے کارکردگی بہتر ہوتی ہے۔” تاہم، زیادہ تر معاملات میں یہ بہتر کارکردگی کا باعث نہیں بنتا اور اکثر ایسا کوڈ تیار کرتا ہے جسے ڈیبگ کرنا زیادہ مشکل ہوتا ہے۔
اس آرٹیکل میں، آپ سیکھیں گے کہ کس طرح اپنے کوڈ کو زیادہ استعمال سے بچنے کے لیے ڈھانچہ بنانا ہے۔ useCallback اور useMemo.
شرطیں
اس ٹیوٹوریل کو پڑھنے سے پہلے آپ کو React ہکس اور اجزاء سے واقف ہونا چاہیے۔ واقف useState، useEffectاور useRef فرض کیا جاتا ہے۔ اگر آپ کو ریفریشر کی ضرورت ہے، تو آپ درج ذیل مفت کوڈ کیمپ مضامین پڑھ سکتے ہیں: useCallback اور useMemo:
انڈیکس
کیا useCallback اور useMemo کرو
اس سے پہلے کہ ہم اس بات کی طرف بڑھیں کہ کس طرح زیادہ استعمال سے بچنا ہے، آئیے ایک سرسری نظر ڈالیں کہ یہ ہکس کیا کرتے ہیں۔
نوٹ پر
useMemo دوبارہ رینڈرنگ کے درمیان فنکشن کی واپسی کی قیمت کو کیش کریں۔ فرض کریں کہ آپ کے اجزاء میں اشیاء کی ترتیب شدہ فہرست ہے۔
interface Item {
name: string;
createdAt: string;
}
function App() {
// == some other states ==
// == some other states ==
const [items, setItems] = useState- ([]);
const sortedItems = [...items].sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
);
return (
<>
{sortedItems.map((i) => (
- {i.name}
))}
>
);
}
React recalculates. sortedItems ہر بار App جزو کو دوبارہ رینڈر کریں۔ اس کا مطلب ہے: sortedItems جب بھی ریاست تبدیل ہوتی ہے اس کا دوبارہ حساب لگایا جاتا ہے۔ App عنصر
React ڈویلپرز کے ذریعہ اکثر استعمال کیا جاتا ہے۔ useMemo کیشے کی قدریں اس طرح ہیں:
اسے لپیٹ دو useMemo گارنٹی sortedItems یہ صرف اس صورت میں شمار کیا جاتا ہے جب: items یہاں اصل میں کیا تبدیلیاں آتی ہیں:
const sortedItems = useMemo(() => {
return [...items].sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
);
}, [items]);
کال بیکس استعمال کریں۔
useCallback فنکشن کو ہی کیش کریں۔ جب بھی جزو کی کچھ حالت تبدیل ہوتی ہے تو نیچے کا فنکشن دوبارہ تخلیق کیا جائے گا۔
function App() {
// == some other states ==
// == some other states ==
const [userId, setUserId] = useState(0);
const verifyUser = async () => {
// update a state to show loading
console.log("__ Do something with user id __", userId);
// update a state to remove loading
};
return (
<>
>
);
}
اسے لپیٹ دو useCallback ایک ہی فنکشن کا حوالہ رکھیں۔ userId غیر تبدیل شدہ:
function App() {
// == some other states ==
// == some other states ==
const [userId, setUserId] = useState(0);
const verifyUser = useCallback(async () => {
// update a state to show loading
console.log("__ Do something with user id __", userId);
// update a state to remove loading
}, [userId]);
return (
<>
>
);
}
میمو مسئلہ
زندگی میں کچھ بھی مفت نہیں ہے۔ میمو بھی اس سے مستثنیٰ نہیں ہیں۔ جب بھی میں اسے استعمال کرتا ہوں۔ useCallback یا useMemo:
یہ نوٹ زیادہ تر معاملات میں مفید نہیں ہے۔ جاوا اسکرپٹ فنکشنز بنانا سستا ہے۔ 50 اشیاء کی فہرست کو ترتیب دینا سستا ہے۔ اسے نوٹ ہک کے ساتھ لپیٹنے سے روک تھام سے زیادہ لاگت آئے گی۔ (تاہم، اگر پروفائلنگ سے پتہ چلتا ہے کہ چھانٹنا رکاوٹ کا باعث بن رہا ہے، useMemo یہ اب بھی اس مقام تک معقول ہے۔)
ایک بہتر نقطہ نظر یہ ہے کہ آپ اپنے اجزاء کی تشکیل کریں تاکہ وہ کم کثرت سے دوبارہ پیش کیے جائیں۔
مسئلہ والا صفحہ
اس کو عملی شکل میں دیکھنے کے لیے، آئیے ایک تلاش کا صفحہ دیکھیں، جہاں ایک بنیادی جزو پورے صفحہ کے لیے تمام حالت اور منطق کا انتظام کرتا ہے۔
کوڈ لکھنے کے لیے، میں نے جو آسان Next.js پروجیکٹ ترتیب دیا ہے اسے کلون کریں۔
git clone https://github.com/Olaleye-Blessing/freecodecamp-usecallback-usememo.git
# navigate to the folder
cd freecodecamp-usecallback-usememo
# install the packages
pnpm install
# start development
pnpm dev
تلاش کا صفحہ پر مشتمل ہے:
مذکورہ بالا تمام ذیلی اجزاء ریاست اور فعالیت کو برقرار نہیں رکھتے ہیں۔ وہ سب اپنی ریاستیں اور افعال اس سے اخذ کرتے ہیں: SearchPage عنصر
ہم بچوں کے اجزاء کا احاطہ نہیں کریں گے۔ یہ صرف UI پیش کرتا ہے۔ ان کے پاس کوئی منطق نہیں ہے۔
کہ SearchPage اجزاء ہیں:
"use client";
import { ChangeEvent, useEffect, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
fetchColors,
fetchCountries,
fetchModes,
fetchProducts,
} from "./utils";
import { Header } from "./_components/header";
import { FilterDrawer } from "./_components/filter-drawer";
import { ProductTable } from "./_components/products-table";
import { FilterChips } from "./_components/filter-chips";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { FilterState, LocalSortField, SortDir, SortField } from "./interfaces";
const DEFAULTS: FilterState = {
query: "",
country: "",
color: "",
mode: "",
minPrice: "",
maxPrice: "",
sortField: "name",
sortDir: "asc",
};
export default function SearchPage() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [drawerOpen, setDrawerOpen] = useState(false);
const [localSort, setLocalSort] = useState<{
field: LocalSortField;
dir: "asc" | "desc";
} | null>(null);
const searchRef = useRef(null);
const searchTimerRef = useRef | null>(null);
const filters: FilterState = {
query: searchParams.get("q") ?? DEFAULTS.query,
country: searchParams.get("country") ?? DEFAULTS.country,
color: searchParams.get("color") ?? DEFAULTS.color,
mode: searchParams.get("mode") ?? DEFAULTS.mode,
minPrice: searchParams.get("minPrice") ?? DEFAULTS.minPrice,
maxPrice: searchParams.get("maxPrice") ?? DEFAULTS.maxPrice,
sortField:
(searchParams.get("sortField") as SortField) ?? DEFAULTS.sortField,
sortDir: (searchParams.get("sortDir") as SortDir) ?? DEFAULTS.sortDir,
};
const apiFilters = {
query: filters.query,
country: filters.country || undefined,
color: filters.color || undefined,
mode: filters.mode || undefined,
minPrice: filters.minPrice ? Number(filters.minPrice) : undefined,
maxPrice: filters.maxPrice ? Number(filters.maxPrice) : undefined,
sortField: filters.sortField,
sortDir: filters.sortDir,
};
const productsQuery = useQuery({
queryKey: ["products", apiFilters],
queryFn: () => fetchProducts(apiFilters),
});
const countriesQuery = useQuery({
queryKey: ["countries"],
queryFn: fetchCountries,
staleTime: Infinity,
});
const colorsQuery = useQuery({
queryKey: ["colors"],
queryFn: fetchColors,
staleTime: Infinity,
});
const modesQuery = useQuery({
queryKey: ["modes"],
queryFn: fetchModes,
staleTime: Infinity,
});
// Updates the filter in the drawer
const setFilters = (partial: Partial) => {
const next = new URLSearchParams(searchParams.toString());
const merged = { ...filters, ...partial };
const keyMap: Record = {
query: "q",
country: "country",
color: "color",
mode: "mode",
minPrice: "minPrice",
maxPrice: "maxPrice",
sortField: "sortField",
sortDir: "sortDir",
};
(Object.keys(merged) as (keyof FilterState)[]).forEach((k) => {
const paramKey = keyMap[k];
const val = merged[k];
const def = DEFAULTS[k];
if (val && val !== def) {
next.set(paramKey, val);
} else {
next.delete(paramKey);
}
});
router.push(`({pathname}?){next.toString()}`, { scroll: false });
};
const resetFilters = () => {
router.push(pathname, { scroll: false });
};
const handleQueryChange = (e: ChangeEvent) => {
const val = e.target.value;
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
searchTimerRef.current = setTimeout(() => {
setFilters({ query: val });
}, 400);
};
const handleClearQuery = () => {
if (searchRef.current) {
searchRef.current.value = "";
}
setFilters({ query: "" });
};
const handleColumnClick = (field: LocalSortField) => {
setLocalSort((prev) => {
if (!prev || prev.field !== field) return { field, dir: "asc" };
if (prev.dir === "asc") return { field, dir: "desc" };
return null;
});
};
const hasPriceFilter = filters.minPrice || filters.maxPrice;
const priceLabel = [
filters.minPrice ? `$${filters.minPrice}` : null,
filters.maxPrice ? `$${filters.maxPrice}` : null,
]
.filter(Boolean)
.join(" - ");
const activeFilterCount = [
filters.country,
filters.color,
filters.mode,
filters.minPrice,
filters.maxPrice,
].filter(Boolean).length;
let sortedProducts = [...(productsQuery.data || [])];
if (localSort) {
sortedProducts = [...sortedProducts].sort((a, b) => {
const aVal = a[localSort.field];
const bVal = b[localSort.field];
const cmp =
typeof aVal === "string"
? aVal.localeCompare(bVal as string)
: (aVal as number) - (bVal as number);
return localSort.dir === "desc" ? -cmp : cmp;
});
}
useEffect(() => {
return () => {
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
};
}, []);
return (
setDrawerOpen((v) => !v)}
activeFilterCount={activeFilterCount}
searchRef={searchRef}
handleChange={handleQueryChange}
/>
setDrawerOpen(false)}
filters={filters}
onChange={setFilters}
onReset={resetFilters}
countries={countriesQuery.data ?? []}
colors={colorsQuery.data ?? []}
modes={modesQuery.data ?? []}
activeFilterCount={activeFilterCount}
/>
{activeFilterCount > 0 && (
)}
);
}
کہ SearchPage جزو صفحہ کو رینڈر کرنے کے لیے درکار تمام منطقوں پر نظر رکھتا ہے۔
-
کہ
products،countries،colorsاورmodes. یہ گزر جاتا ہےcountries،colorsاورmodesدراز کے اجزاء پر -
دراز کی حیثیت کو ٹریک کریں۔
-
مقامی طور پر مصنوعات کو ترتیب دینے کے لیے درکار افعال کی وضاحت کریں، وغیرہ۔
یہاں مسئلہ یہ ہے کہ اگر ریاستوں میں سے ایک بدل جاتی ہے تو تمام افعال دوبارہ بنائے جاتے ہیں۔ SearchPage عنصر مثال کے طور پر isLoading پر useQuery مصنوعات (productsQuery) سے بدل گیا۔ false کو trueتمام افعال اور اخذ کردہ قدریں دوبارہ تخلیق کی جاتی ہیں۔
پہلی چیز جو ذہن میں آتی ہے وہ ہے کیشنگ فنکشنز اور اخذ کردہ اقدار کا استعمال کرتے ہوئے: useCallback اور useMemo. جب کہ یہ کام کرتا ہے، یہ اس صفحہ پر غیر ضروری کارکردگی کا اضافہ کرتا ہے۔
ایک بہتر حل یہ ہوگا کہ ریاست اور منطق کو اس کے قریب منتقل کیا جائے جہاں وہ اصل میں استعمال ہوتے ہیں۔
کسی ریاست کو کیسے نیچے منتقل کیا جائے۔
یہاں خیال ہے: اگر صرف ایک جزو کو ریاست یا افعال کی ضرورت ہے، تو اس جزو کو اس کا مالک ہونا چاہیے۔ اگر بچے کا جزو اپنی حالت کا خود انتظام کرتا ہے، تو اس کی حالت کو تبدیل کرنے سے والدین کا جزو دوبارہ پیش نہیں ہوگا۔ اس کا مطلب ہے کہ تمام بہن بھائیوں کی حالت اور فعالیت بغیر کسی نوٹ کے مستحکم رہتی ہے۔
دوسرے الفاظ میں، منطق کو اتنا نیچے نہ لے جائیں کہ مشترکہ رویے کو جانچنا یا ایڈجسٹ کرنا زیادہ مشکل ہو جائے۔ مقصد یہ نہیں ہے کہ منطق کے ہر ٹکڑے کو ممکنہ گہرے جزو کے اندر چھپایا جائے۔ مقصد ریاست اور منطق کو نچلی سطح پر رکھنا ہے جو اب بھی فعالیت کے لیے موزوں ہے۔
پروڈکٹ ٹیبل منطق کو اس جزو میں منتقل کریں۔
اگر آپ مصنوعات کو حاصل کرنے اور ترتیب دینے کا طریقہ دیکھتے ہیں، تو آپ دیکھیں گے کہ اس ڈیٹا کو استعمال کرنے والے صرف اجزاء ہیں: ProductsTable. اس کا مطلب ہے کہ آپ اپنی درآمد اور ترتیب دینے والی منطق کو اس پر منتقل کر سکتے ہیں: ProductsTable.
کہ ProductsComponent یہ موجودہ حالت اور منطق کو بطور پروپس حاصل کرتا ہے:
"use client";
interface ProductTableProps {
products: Product[];
isLoading: boolean;
handleColumnClick: (field: LocalSortField) => void;
localSort: { field: LocalSortField; dir: SortDir } | null;
}
export function ProductTable({
products,
isLoading,
handleColumnClick,
localSort,
}: ProductTableProps) {
// renders the UI using the props
}
اب ProductTable اس منطق کو درآمد اور منظم کریں۔
interface ProductTableProps {
filters: FilterState;
}
export function ProductTable({ filters }: ProductTableProps) {
const [localSort, setLocalSort] = useState<{
field: LocalSortField;
dir: "asc" | "desc";
} | null>(null);
const apiFilters = {
query: filters.query,
country: filters.country || undefined,
color: filters.color || undefined,
mode: filters.mode || undefined,
minPrice: filters.minPrice ? Number(filters.minPrice) : undefined,
maxPrice: filters.maxPrice ? Number(filters.maxPrice) : undefined,
sortField: filters.sortField,
sortDir: filters.sortDir,
};
const { data: products = [], isLoading } = useQuery({
queryKey: ["products", apiFilters],
queryFn: () => fetchProducts(apiFilters),
});
const handleColumnClick = (field: LocalSortField) => {
setLocalSort((prev) => {
if (!prev || prev.field !== field) return { field, dir: "asc" };
if (prev.dir === "asc") return { field, dir: "desc" };
return null;
});
};
let sortedProducts = products;
if (localSort) {
sortedProducts = [...products].sort((a, b) => {
const aVal = a[localSort.field];
const bVal = b[localSort.field];
const cmp =
typeof aVal === "string"
? aVal.localeCompare(bVal as string)
: (aVal as number) - (bVal as number);
return localSort.dir === "desc" ? -cmp : cmp;
});
}
return <>{/*== renders the UI using the props ==*/}>;
}
اب جب isLoading تبدیلی SearchPage جزو دوبارہ رینڈر نہیں ہوتا ہے۔ اس کا مطلب ہے اخذ کردہ اقدار اور دیگر افعال۔ SearchPage اجزاء دوبارہ پیدا نہیں ہوتے ہیں۔ صرف وہی اقدار اور افعال ہیں جو یہاں دوبارہ بنائے جائیں گے۔ sortedProducts اور handleColumnClick.
کہ SearchPage اجزاء مندرجہ ذیل ہیں:
"use client";
import { ChangeEvent, useEffect, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { fetchColors, fetchCountries, fetchModes } from "./utils";
import { Header } from "./_components/header";
import { FilterDrawer } from "./_components/filter-drawer";
import { ProductTable } from "./_components/products-table";
import { FilterChips } from "./_components/filter-chips";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { FilterState, SortDir, SortField } from "./interfaces";
const DEFAULTS: FilterState = {
query: "",
country: "",
color: "",
mode: "",
minPrice: "",
maxPrice: "",
sortField: "name",
sortDir: "asc",
};
export default function SearchPage() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [drawerOpen, setDrawerOpen] = useState(false);
const searchRef = useRef(null);
const searchTimerRef = useRef | null>(null);
const filters: FilterState = {
query: searchParams.get("q") ?? DEFAULTS.query,
country: searchParams.get("country") ?? DEFAULTS.country,
color: searchParams.get("color") ?? DEFAULTS.color,
mode: searchParams.get("mode") ?? DEFAULTS.mode,
minPrice: searchParams.get("minPrice") ?? DEFAULTS.minPrice,
maxPrice: searchParams.get("maxPrice") ?? DEFAULTS.maxPrice,
sortField:
(searchParams.get("sortField") as SortField) ?? DEFAULTS.sortField,
sortDir: (searchParams.get("sortDir") as SortDir) ?? DEFAULTS.sortDir,
};
const countriesQuery = useQuery({
queryKey: ["countries"],
queryFn: fetchCountries,
staleTime: Infinity,
});
const colorsQuery = useQuery({
queryKey: ["colors"],
queryFn: fetchColors,
staleTime: Infinity,
});
const modesQuery = useQuery({
queryKey: ["modes"],
queryFn: fetchModes,
staleTime: Infinity,
});
// Updates the filter in the drawer
const setFilters = (partial: Partial) => {
const next = new URLSearchParams(searchParams.toString());
const merged = { ...filters, ...partial };
const keyMap: Record = {
query: "q",
country: "country",
color: "color",
mode: "mode",
minPrice: "minPrice",
maxPrice: "maxPrice",
sortField: "sortField",
sortDir: "sortDir",
};
(Object.keys(merged) as (keyof FilterState)[]).forEach((k) => {
const paramKey = keyMap[k];
const val = merged[k];
const def = DEFAULTS[k];
if (val && val !== def) {
next.set(paramKey, val);
} else {
next.delete(paramKey);
}
});
router.push(`({pathname}?){next.toString()}`, { scroll: false });
};
const resetFilters = () => {
router.push(pathname, { scroll: false });
};
const handleQueryChange = (e: ChangeEvent) => {
const val = e.target.value;
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
searchTimerRef.current = setTimeout(() => {
setFilters({ query: val });
}, 400);
};
const handleClearQuery = () => {
if (searchRef.current) {
searchRef.current.value = "";
}
setFilters({ query: "" });
};
const hasPriceFilter = filters.minPrice || filters.maxPrice;
const priceLabel = [
filters.minPrice ? `$${filters.minPrice}` : null,
filters.maxPrice ? `$${filters.maxPrice}` : null,
]
.filter(Boolean)
.join(" - ");
const activeFilterCount = [
filters.country,
filters.color,
filters.mode,
filters.minPrice,
filters.maxPrice,
].filter(Boolean).length;
useEffect(() => {
return () => {
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
};
}, []);
return (
setDrawerOpen((v) => !v)}
activeFilterCount={activeFilterCount}
searchRef={searchRef}
handleChange={handleQueryChange}
/>
setDrawerOpen(false)}
filters={filters}
onChange={setFilters}
onReset={resetFilters}
countries={countriesQuery.data ?? []}
colors={colorsQuery.data ?? []}
modes={modesQuery.data ?? []}
activeFilterCount={activeFilterCount}
/>
{activeFilterCount > 0 && (
)}
);
}
کہ SearchPage جزو اب پروڈکٹ ڈیٹا کی بازیافت اور ترتیب کو برقرار نہیں رکھتا ہے۔
فلٹرنگ منطق کو اس جزو میں منتقل کریں۔
اس کام کو پورا کرنے کے لیے مختلف ریاستیں، ڈیٹا اور افعال موجود ہیں۔
-
drawerOpenاورsetDrawerOpenفلٹر دراز کو کنٹرول کرتا ہے۔ -
countries،colorsاورmodesڈیٹا جو صارف کو مختلف اختیارات منتخب کرنے کی اجازت دیتا ہے۔ -
activeFilterCountفعال فلٹرز کی تعداد دکھاتا ہے۔
فی الحال، فلٹرنگ کے دو اجزاء ہیں: پہلا اندر کا بٹن ہے۔ Header اجزاء جیسے:
;
دوسرا ہے۔ FilterDrawer اجزاء جیسے:
"use client";
import { useEffect, useRef } from "react";
import { X, RotateCcw } from "lucide-react";
import { FilterState } from "../interfaces";
import { SortSection } from "./filter/sort-section";
import { NarrowResultsSection } from "./filter/narrow-result-section";
interface FilterDrawerProps {
open: boolean;
onClose: () => void;
filters: FilterState;
onChange: (partial: Partial) => void;
onReset: () => void;
countries: string[];
colors: string[];
modes: string[];
activeFilterCount: number;
}
export function FilterDrawer({
open,
onClose,
filters,
onChange,
onReset,
countries,
colors,
modes,
activeFilterCount,
}: FilterDrawerProps) {
const drawerRef = useRef(null);
// Close on Escape
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [onClose]);
// Prevent body scroll while open
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "";
return () => {
document.body.style.overflow = "";
};
}, [open]);
return (
<>
{/* Backdrop */}
{/* Drawer panel */}
>
);
}
آپ دو اجزاء کو ایک جزو میں جوڑ سکتے ہیں۔ Filter وہ جز جو اس تمام منطق کا مالک ہے:
import { RotateCcw, SlidersHorizontal, X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { SortSection } from "./filter/sort-section";
import { NarrowResultsSection } from "./filter/narrow-result-section";
import { FilterState } from "../interfaces";
import { usePathname, useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { fetchColors, fetchCountries, fetchModes } from "../utils";
interface FilterProps {
filters: FilterState;
onChange: (partial: Partial) => void;
}
const Filter = ({ filters, onChange }: FilterProps) => {
const { data: countries = [] } = useQuery({
queryKey: ["countries"],
queryFn: fetchCountries,
staleTime: Infinity,
});
const { data: colors = [] } = useQuery({
queryKey: ["colors"],
queryFn: fetchColors,
staleTime: Infinity,
});
const { data: modes = [] } = useQuery({
queryKey: ["modes"],
queryFn: fetchModes,
staleTime: Infinity,
});
const router = useRouter();
const pathname = usePathname();
const [drawerOpen, setDrawerOpen] = useState(false);
const drawerRef = useRef(null);
const onClose = () => setDrawerOpen(false);
const openDrawer = () => setDrawerOpen(true);
const resetFilters = () => {
onClose();
router.push(pathname, { scroll: false });
};
const activeFilterCount = [
filters.country,
filters.color,
filters.mode,
filters.minPrice,
filters.maxPrice,
].filter(Boolean).length;
// Close on Escape
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") setDrawerOpen(false);
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
// Prevent body scroll while open
useEffect(() => {
document.body.style.overflow = drawerOpen ? "hidden" : "";
return () => {
document.body.style.overflow = "";
};
}, [drawerOpen]);
return (
<>
{/* Backdrop */}
{/* Drawer panel */}
>
);
};
export default Filter;
ہم اسے مزید ترقی دے سکتے ہیں۔ توجہ فرمائیں NarrowResultsSection یہ واحد جزو ہے جو درآمد شدہ اشیاء استعمال کرتا ہے۔ countries، colorsاور modes. اور ہر ایک میں SelectField ہم اس میں سے کچھ ڈیٹا استعمال کرتے ہیں۔
import { FilterState } from "../../interfaces";
import { PriceRangeField } from "./price-range";
import { SelectField } from "./select-field";
interface NarrowResultsSectionProps {
filters: FilterState;
onChange: (partial: Partial) => void;
countries: string[];
colors: string[];
modes: string[];
}
export function NarrowResultsSection({
filters,
onChange,
countries,
colors,
modes,
}: NarrowResultsSectionProps) {
return (
Narrow Results
onChange({ country: v })}
placeholder="All countries"
/>
onChange({ color: v })}
placeholder="All colors"
/>
onChange({ mode: v })}
placeholder="All modes"
/>
);
}
تمام اشیاء کو اوپر سے لے کر نیچے سے گزرنے کے بجائے، آپ ہر چیز کو جانے دے سکتے ہیں۔ SelectField یہ اس کا اپنا استفسار ہے۔
کہ SelectField یہ اس طرح نظر آیا:
interface SelectFieldProps {
label: string;
value: string;
options: string[];
onChange: (v: string) => void;
placeholder: string;
}
export function SelectField({
label,
value,
options,
onChange,
placeholder,
}: SelectFieldProps) {
return <>{/*=== Renders UI ===*/}>;
}
اب یہ اس طرح لگتا ہے:
import { useQuery } from "@tanstack/react-query";
interface SelectFieldProps {
label: string;
value: string;
onChange: (v: string) => void;
placeholder: string;
queryFn(): Promise;
queryKey: string;
}
export function SelectField({
label,
value,
onChange,
placeholder,
queryFn,
queryKey,
}: SelectFieldProps) {
const { data: options = [] } = useQuery({
queryKey: [queryKey],
queryFn: queryFn,
staleTime: Infinity,
});
return <>{/*=== Renders UI ===*/}>;
}
ہر ڈراپ ڈاؤن اب اپنے ڈیٹا کا انتظام کرتا ہے۔ ایک کے اندر ریاست کو تبدیل کریں۔ SelectField یہ بہن بھائیوں یا والدین کو متاثر نہیں کرتا ہے۔
تلاش کی منطق کو اس جزو میں منتقل کریں۔
کہ Search جزو واحد ہے جو ڈیبونسنگ منطق کا استعمال کرتا ہے (searchTimerRef، handleQueryChange، handleClearQuery)۔ منطق کو جزو کے اندر منتقل کریں۔
"use client";
import { Search as SearchIcon, X } from "lucide-react";
import { ChangeEvent, useEffect, useRef } from "react";
import { FilterState } from "../interfaces";
interface SearchProps {
query: string;
onChange: (partial: Partial) => void;
}
const Search = ({ query, onChange }: SearchProps) => {
const searchRef = useRef(null);
const searchTimerRef = useRef | null>(null);
const handleClearQuery = () => {
if (searchRef.current) {
searchRef.current.value = "";
}
onChange({ query: "" });
};
const handleQueryChange = (e: ChangeEvent) => {
const val = e.target.value;
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
searchTimerRef.current = setTimeout(() => {
onChange({ query: val });
}, 400);
};
useEffect(() => {
return () => {
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
};
}, []);
return {/*=== Renders UI ===*/}
;
};
export default Search;
فلٹر چپ کو اس کے جزو میں منتقل کریں۔
کہ FilterChips جزو فعال فلٹر کے لیے چپ پیش کرتا ہے۔ کہ hasPriceFilter اور priceLabel قیمت جو یہ فراہم کرتی ہے اس کے بجائے جزو کے اندر ہوسکتی ہے۔ SearchPage:
interface FilterChipsProps {
filters: FilterState;
setFilters: (partial: Partial) => void;
resetFilters: () => void;
}
export function FilterChips({
filters,
setFilters,
resetFilters,
}: FilterChipsProps) {
const priceLabel = [
filters.minPrice ? `$${filters.minPrice}` : null,
filters.maxPrice ? `$${filters.maxPrice}` : null,
]
.filter(Boolean)
.join(" - ");
const hasPriceFilter = filters.minPrice || filters.maxPrice;
return <>{/*=== Renders UI ===*/}>;
}
حتمی تلاش کا صفحہ
تمام حالت اور منطق کو مطلوبہ اجزاء میں منتقل کرنے کے بعد: SearchPage اجزاء ہیں:
"use client";
import { Header } from "./_components/header";
import { ProductTable } from "./_components/products-table";
import { FilterChips } from "./_components/filter-chips";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { FilterState, SortDir, SortField } from "./interfaces";
const DEFAULTS: FilterState = {
query: "",
country: "",
color: "",
mode: "",
minPrice: "",
maxPrice: "",
sortField: "name",
sortDir: "asc",
};
export default function SearchPage() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const filters: FilterState = {
query: searchParams.get("q") ?? DEFAULTS.query,
country: searchParams.get("country") ?? DEFAULTS.country,
color: searchParams.get("color") ?? DEFAULTS.color,
mode: searchParams.get("mode") ?? DEFAULTS.mode,
minPrice: searchParams.get("minPrice") ?? DEFAULTS.minPrice,
maxPrice: searchParams.get("maxPrice") ?? DEFAULTS.maxPrice,
sortField:
(searchParams.get("sortField") as SortField) ?? DEFAULTS.sortField,
sortDir: (searchParams.get("sortDir") as SortDir) ?? DEFAULTS.sortDir,
};
const setFilters = (partial: Partial) => {
const next = new URLSearchParams(searchParams.toString());
const merged = { ...filters, ...partial };
const keyMap: Record = {
query: "q",
country: "country",
color: "color",
mode: "mode",
minPrice: "minPrice",
maxPrice: "maxPrice",
sortField: "sortField",
sortDir: "sortDir",
};
(Object.keys(merged) as (keyof FilterState)[]).forEach((k) => {
const paramKey = keyMap[k];
const val = merged[k];
const def = DEFAULTS[k];
if (val && val !== def) {
next.set(paramKey, val);
} else {
next.delete(paramKey);
}
});
router.push(`({pathname}?){next.toString()}`, { scroll: false });
};
const resetFilters = () => {
router.push(pathname, { scroll: false });
};
const activeFilterCount = [
filters.country,
filters.color,
filters.mode,
filters.minPrice,
filters.maxPrice,
].filter(Boolean).length;
return (
{activeFilterCount > 0 && (
)}
);
}
توجہ فرمائیں setFilters، resetFilters اور activeFilterCount یہ اب بھی وہاں ہے۔ SearchPage عنصر یہ جان بوجھ کر ہے۔ یہ قدر URL کے لحاظ سے مختلف ہوتی ہے۔ یو آر ایل سے پڑھنے والا کوئی بھی جزو جب بھی یو آر ایل تبدیل ہوتا ہے اسے دوبارہ پیش کیا جائے گا۔ اس سے کوئی فرق نہیں پڑتا کہ قیمت کا حساب کہاں لگایا جاتا ہے۔
ان ہکس تک پہنچنے سے پہلے اپنا کوڈ درست کریں۔
آپ تک پہنچنے کا لالچ ہو سکتا ہے۔ useCallback یا useMemo جب لامحدود دوبارہ رینڈرنگ ہوتے ہیں۔ غیر مستحکم آبجیکٹ کے حوالہ جات اکثر لامحدود دوبارہ پیش کرنے کا نتیجہ ہوتے ہیں۔ خاص طور پر ذیلی اجزاء میں useEffect یہ اعتراض پر منحصر ہے۔ یہ سمجھنا ہمیشہ بہتر ہے کہ لوپ کیوں ہوتا ہے اور اس کی بنیادی وجہ کو حل کریں۔
اس مثال کو دیکھیں:
"use client";
import { useEffect, useState } from "react";
import { fetchUsers, Filter, User } from "./utils";
interface UserListProps {
filters: Filter;
onLoad(data: User[]): void;
users: User[];
}
function UserList({ filters, onLoad, users }: UserListProps) {
console.count("__ USER LIST __");
useEffect(() => {
fetchUsers(filters).then((data) => {
onLoad(data);
});
}, [filters, onLoad]);
return (
{users.map((u) => (
- {u.name}
))}
);
}
const UserPage = () => {
const [userData, setUserData] = useState([]);
const filters = {
role: "admin",
active: true,
};
return ;
};
export default UserPage;
کہ UserList جب نصب کیا جاتا ہے تو جزو صارف کو ملتا ہے۔ یہ استعمال کرتا ہے filters اور onLoad انحصار کے طور پر۔
مسئلہ یہاں ہے۔ filters کا اعتراض UserPage اجزاء کو جب بھی رینڈر کیا جاتا ہے دوبارہ تخلیق کیا جاتا ہے۔ یہاں تک کہ اگر قدریں ایک جیسی نظر آتی ہیں، تب بھی وہ نئے حوالہ جات ہیں جب بھی وہ دوبارہ پیش کیے جاتے ہیں۔ UserList اسے ہر بار ایک نئی قدر سمجھا جاتا ہے۔ یہ ہے useEffect کیونکہ یہ ایک انحصار ہے۔

ریپنگ filters کو useMemo یہ لوپ کو روکتا ہے، لیکن اصل مسئلہ کو یاد کرتا ہے. useMemo ہم لامحدود دوبارہ رینڈرنگ کو روکنے کی کوشش نہیں کر رہے ہیں۔ اس مسئلے کو حل کرنے کے لیے کچھ بہتر حل ہیں۔
پہلا آپشن یہ ہے کہ آبجیکٹ کے بجائے انحصاری سرنی کی قدیم قسم کا استعمال کریں۔ آبجیکٹ کا حوالہ سے موازنہ کیا جاتا ہے۔ یہ ہے useEffect ہر بار جب آپ اسے پڑھیں گے، آپ کو ایک مختلف حوالہ نظر آئے گا۔ filter سہارے قدیم عناصر کا قدر سے موازنہ کیا جاتا ہے۔
function UserList({ filters, onLoad, users }: UserListProps) {
console.count("__ USER LIST __");
useEffect(() => {
fetchUsers(filters).then((data) => {
onLoad(data);
});
}, [filters.active, filters.role]); // primitives compare by value, not reference
return (
{users.map((u) => (
- {u.name}
))}
);
}
دوسرا آپشن یہ ہے کہ جزو سے باہر آبجیکٹ کی وضاحت کی جائے تاکہ آپ کے پاس اس کا مستحکم حوالہ ہو۔
const filters = {
role: "admin",
active: true,
};
const UserPage = () => {
const [userData, setUserData] = useState([]);
return ;
};
تیسرا حل یہ ہے کہ آبجیکٹ کو حالت میں محفوظ کیا جائے اگر شے متحرک ہے۔
const UserPage = () => {
const [userData, setUserData] = useState([]);
const [filters, setFilters] = useState({
role: "admin",
active: true,
});
return ;
};
کب استعمال کرنا ہے۔ useCallback اور useMemo
اس مضمون کا مقصد آپ کو یہ بتانا نہیں ہے کہ ان ہکس کو کبھی استعمال نہ کریں۔ حقیقی زندگی کے حالات ہیں جہاں یہ ہکس چمکتے ہیں۔
اصلاح کرنے سے پہلے پیمائش کریں۔
اصلاح کے ساتھ آگے بڑھنے سے پہلے useCallback، useMemo, متبادل طور پر، اگر آپ کسی جزو کو دوبارہ ترتیب دے رہے ہیں، تو آپ کو پہلے کارکردگی کے مسائل کی جانچ کرنی چاہیے۔ کوڈ کو بہتر بنانا جس کی ضرورت نہیں ہے کسی کو فائدہ نہیں دیتا۔
React DevTools میں ایک پروفائلر ٹیب ہے جو آپ کو اپنے سیشن کو لاگ ان کرنے اور یہ دیکھنے دیتا ہے کہ کون سے اجزاء کو دوبارہ رینڈر کیا جا رہا ہے، کتنی بار، اور ہر رینڈر میں کتنا وقت لگتا ہے۔ React ڈویلپر ٹولز کا استعمال کیسے کریں – مثالوں کے ساتھ وضاحت پڑھیں۔ اگر آپ ویڈیو کے پرستار ہیں، تو آپ بین کو کارکردگی کے مسائل کو تلاش کرنے اور ان کو ٹھیک کرنے کے لیے React پروفائلر کو استعمال کرنے کا مظاہرہ کرتے ہوئے دیکھ سکتے ہیں۔
حوالہ جات کو مستحکم کریں۔ React.memo بچے
React.memo اگر پرپس تبدیل نہیں ہوئے ہیں تو جزو کو دوبارہ پیش کرنے سے روکتا ہے۔ تاہم، اگر آپ کسی فنکشن یا آبجیکٹ کو پروپ کے طور پر پاس کرتے ہیں، تو بچے کو ہر پیرنٹ رینڈر پر دوبارہ رینڈر کیا جاتا رہے گا کیونکہ فنکشن اور آبجیکٹ کو ہر بار ایک نئے حوالہ کے ساتھ دوبارہ بنایا جاتا ہے۔
اب اسے استعمال کرنے کا بہترین وقت ہے۔ useCallback یا useMemo:
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
console.log("Child rendered");
return ;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<>
>
);
}
بغیر useCallback، Child ہر بار دوبارہ رینڈر کریں۔ count چاہے وہ بدل جائے۔ handleClick کے ساتھ کوئی تعلق نہیں ہے count. کے ساتھ useCallbackفنکشن حوالہ جات مستحکم رہتے ہیں۔
دونوں کو استعمال کرنا اکثر بہتر ہوتا ہے۔ useCallback اور React.memo ایک ساتھ لیکن React.memo اگر پرپس قدیم یا مستحکم ہیں، تو وہ اپنے آپ میں مفید ہو سکتے ہیں۔ اور useCallback باہر بھی مفید ہو سکتا ہے۔ React.memoمثال کے طور پر، قابل اعتماد کال بیکس کو ایفیکٹس، کسٹم ہکس، یا تھرڈ پارٹی اجزاء میں منتقل کرتے وقت۔
نتیجہ
useCallback اور useMemo یہ نوٹ لینے کا ایک مفید ٹول ہے، لیکن یہ مفت پرفارمنس اپ گریڈ نہیں ہے۔ ہر کال ہر رینڈر میں میموری اوور ہیڈ اور انحصار کا موازنہ شامل کرتی ہے۔
ہمیشہ اپنے اجزاء کو ترتیب دیں تاکہ بہت کم اصلاح کی ضرورت ہو۔ ریاست اور منطق کو اس کے استعمال کرنے والے اجزاء کے جتنا ممکن ہو قریب منتقل کریں۔ استعمال کریں useCallback اور useMemo ایک ساتھ React.memo اس بات کی تصدیق کرنے کے بعد کہ رینڈرنگ واقعی مسئلہ ہے۔