diff --git a/src/components/section.tsx b/src/components/section.tsx
index debc4f3..846de01 100644
--- a/src/components/section.tsx
+++ b/src/components/section.tsx
@@ -1,8 +1,9 @@
"use client";
-import { ReactNode, useEffect, useRef, useState } from "react";
+import { memo, ReactNode, useEffect, useRef, useState } from "react";
-export const Section = ({
+// ⚡ Optimization: Section component is memoized to prevent redundant re-renders of the section wrapper.
+export const Section = memo(({
id,
title,
children,
@@ -62,4 +63,6 @@ export const Section = ({
);
-};
+});
+
+Section.displayName = "Section";
diff --git a/src/components/sections/education.tsx b/src/components/sections/education.tsx
index 34c6ee1..b88a43a 100644
--- a/src/components/sections/education.tsx
+++ b/src/components/sections/education.tsx
@@ -4,9 +4,14 @@ import Image from "next/image";
import degreesData from "@/data/degrees";
+import { memo } from "react";
+
import { Section } from "../section";
-export const EducationSection = () => {
+// ⚡ Optimization: EducationSection is memoized to prevent unnecessary re-renders.
+// Since it only depends on static data (degreesData) and doesn't consume filter/search contexts,
+// it should only re-render if its parent (Home) re-renders, which we also want to avoid.
+export const EducationSection = memo(() => {
return (
{degreesData.map((degree) => (
@@ -36,4 +41,6 @@ export const EducationSection = () => {
))}
);
-};
+});
+
+EducationSection.displayName = "EducationSection";
diff --git a/src/components/sections/skills.tsx b/src/components/sections/skills.tsx
index bd8b3c9..206d510 100644
--- a/src/components/sections/skills.tsx
+++ b/src/components/sections/skills.tsx
@@ -14,19 +14,25 @@ export const SkillsSection = () => {
const { debouncedQuery } = useSearch();
const { selected } = useFilter();
- const filteredSkills = useMemo(() => {
- const lowercaseQuery = debouncedQuery.toLowerCase();
+ const areas = selected["areas"];
- // Determine which skills are matching the area filter
- const selectedAreas = selected["areas"] || [];
- const areaMatchingSkills = new Set();
- if (selectedAreas.length === 0) {
- Object.keys(skillsData).forEach((s) => areaMatchingSkills.add(s));
+ // ⚡ Optimization: Memoize areaMatchingSkills separately from the query filter.
+ // This avoids re-calculating the matching skills set when only the search query changes.
+ const areaMatchingSkills = useMemo(() => {
+ const matchingSkills = new Set();
+ if (!areas || areas.length === 0) {
+ Object.keys(skillsData).forEach((s) => matchingSkills.add(s));
} else {
- selectedAreas.forEach((area) => {
- areaSkills[area]?.forEach((skill) => areaMatchingSkills.add(skill));
+ areas.forEach((area) => {
+ areaSkills[area]?.forEach((skill) => matchingSkills.add(skill));
});
}
+ return matchingSkills;
+ }, [areas]);
+
+ // ⚡ Optimization: filteredSkills now only depends on debouncedQuery and the memoized areaMatchingSkills.
+ const filteredSkills = useMemo(() => {
+ const lowercaseQuery = debouncedQuery.toLowerCase();
return Object.entries(skillsData).filter(([key, skill]) => {
const matchesQuery =
@@ -34,7 +40,7 @@ export const SkillsSection = () => {
const matchesArea = areaMatchingSkills.has(key);
return matchesQuery && matchesArea;
});
- }, [debouncedQuery, selected]);
+ }, [debouncedQuery, areaMatchingSkills]);
return (