From 329c11fad02c033fc5c8cdd619eaca066ee6f700 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 20:07:10 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Memoize=20filtered=20list?= =?UTF-8?q?=20arrays=20to=20improve=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrapped filtered lists in `Forum.jsx` and `Roadmap.jsx` with `useMemo` - Moved static `externalResources` array outside of the component definition in `Documentaries.jsx` and wrapped `filteredResources` in `useMemo` - Prevents expensive array calculations and filtering logic on every render Co-authored-by: belpythons <187399139+belpythons@users.noreply.github.com> --- .jules/bolt.md | 3 + src/pages/Documentaries.jsx | 204 ++++++++++++++++++------------------ src/pages/Forum.jsx | 24 +++-- src/pages/Roadmap.jsx | 50 ++++----- 4 files changed, 145 insertions(+), 136 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..be4f63b --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-09 - Extracted Static Arrays to Prevent Unnecessary Renders +**Learning:** In components like `Documentaries.jsx`, static configuration arrays (like `externalResources`) were defined directly inside the component body. This causes them to be recreated on every single render, which completely breaks referential equality checks for downstream hooks like `useMemo` or child components wrapped in `React.memo`, leading to subtle and frequent performance issues. +**Action:** When attempting to memoize derived state or optimize components, always inspect if there are locally defined objects or arrays being used as dependencies. If they don't depend on component state or props, explicitly move them outside the component definition to ensure a stable reference before applying `useMemo`. \ No newline at end of file diff --git a/src/pages/Documentaries.jsx b/src/pages/Documentaries.jsx index 8befef4..7a02c97 100644 --- a/src/pages/Documentaries.jsx +++ b/src/pages/Documentaries.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useMemo } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBook, @@ -26,6 +26,97 @@ import ErrorMessage from "../components/common/ErrorMessage"; import SkeletonLoader from "../components/common/SkeletonLoader"; import Button from "../components/common/Button"; +const externalResources = [ + { + id: 1, + title: "W3Schools HTML Tutorial", + description: "Complete HTML tutorial with examples and exercises", + url: "https://www.w3schools.com/html/", + category: "html", + type: "Tutorial", + difficulty: "Beginner", + icon: faHtml5, + color: "bg-orange-100 text-orange-600", + }, + { + id: 2, + title: "W3Schools CSS Tutorial", + description: "Learn CSS from basic to advanced with interactive examples", + url: "https://www.w3schools.com/css/", + category: "css", + type: "Tutorial", + difficulty: "Beginner", + icon: faCss3Alt, + color: "bg-blue-100 text-blue-600", + }, + { + id: 3, + title: "JavaScript Guide - MDN", + description: "Comprehensive JavaScript documentation and guide", + url: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide", + category: "javascript", + type: "Documentation", + difficulty: "Intermediate", + icon: faJsSquare, + color: "bg-yellow-100 text-yellow-600", + }, + { + id: 4, + title: "Python Official Tutorial", + description: "Official Python tutorial from python.org", + url: "https://docs.python.org/3/tutorial/", + category: "python", + type: "Official Docs", + difficulty: "Beginner", + icon: faPython, + color: "bg-green-100 text-green-600", + }, + { + id: 5, + title: "React Documentation", + description: "Official React documentation and learning resources", + url: "https://react.dev/", + category: "react", + type: "Official Docs", + difficulty: "Intermediate", + icon: faReact, + color: "bg-cyan-100 text-cyan-600", + }, + { + id: 6, + title: "Node.js Getting Started", + description: "Official Node.js guides and API documentation", + url: "https://nodejs.org/en/learn/getting-started/introduction-to-nodejs", + category: "node", + type: "Official Docs", + difficulty: "Intermediate", + icon: faNodeJs, + color: "bg-green-100 text-green-500", + }, + { + id: 7, + title: "SQL Tutorial - W3Schools", + description: "Learn SQL for database management", + url: "https://www.w3schools.com/sql/", + category: "database", + type: "Tutorial", + difficulty: "Beginner", + icon: faDatabase, + color: "bg-purple-100 text-purple-600", + }, + { + id: 8, + title: "Docker Get Started", + description: "Official Docker tutorial and documentation", + url: "https://docs.docker.com/get-started/", + category: "devops", + type: "Official Docs", + difficulty: "Intermediate", + icon: faDocker, + color: "bg-blue-100 text-blue-500", + }, +]; + const Documentaries = () => { const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); @@ -58,108 +149,19 @@ const Documentaries = () => { }, ]; - const externalResources = [ - { - id: 1, - title: "W3Schools HTML Tutorial", - description: "Complete HTML tutorial with examples and exercises", - url: "https://www.w3schools.com/html/", - category: "html", - type: "Tutorial", - difficulty: "Beginner", - icon: faHtml5, - color: "bg-orange-100 text-orange-600", - }, - { - id: 2, - title: "W3Schools CSS Tutorial", - description: "Learn CSS from basic to advanced with interactive examples", - url: "https://www.w3schools.com/css/", - category: "css", - type: "Tutorial", - difficulty: "Beginner", - icon: faCss3Alt, - color: "bg-blue-100 text-blue-600", - }, - { - id: 3, - title: "JavaScript Guide - MDN", - description: "Comprehensive JavaScript documentation and guide", - url: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide", - category: "javascript", - type: "Documentation", - difficulty: "Intermediate", - icon: faJsSquare, - color: "bg-yellow-100 text-yellow-600", - }, - { - id: 4, - title: "Python Official Tutorial", - description: "Official Python tutorial from python.org", - url: "https://docs.python.org/3/tutorial/", - category: "python", - type: "Official Docs", - difficulty: "Beginner", - icon: faPython, - color: "bg-green-100 text-green-600", - }, - { - id: 5, - title: "React Documentation", - description: "Official React documentation and learning resources", - url: "https://react.dev/", - category: "react", - type: "Official Docs", - difficulty: "Intermediate", - icon: faReact, - color: "bg-cyan-100 text-cyan-600", - }, - { - id: 6, - title: "Node.js Getting Started", - description: "Official Node.js guides and API documentation", - url: "https://nodejs.org/en/learn/getting-started/introduction-to-nodejs", - category: "node", - type: "Official Docs", - difficulty: "Intermediate", - icon: faNodeJs, - color: "bg-green-100 text-green-500", - }, - { - id: 7, - title: "SQL Tutorial - W3Schools", - description: "Learn SQL for database management", - url: "https://www.w3schools.com/sql/", - category: "database", - type: "Tutorial", - difficulty: "Beginner", - icon: faDatabase, - color: "bg-purple-100 text-purple-600", - }, - { - id: 8, - title: "Docker Get Started", - description: "Official Docker tutorial and documentation", - url: "https://docs.docker.com/get-started/", - category: "devops", - type: "Official Docs", - difficulty: "Intermediate", - icon: faDocker, - color: "bg-blue-100 text-blue-500", - }, - ]; - - const filteredResources = externalResources.filter((resource) => { - const matchesSearch = - !searchTerm || - resource.title.toLowerCase().includes(searchTerm.toLowerCase()) || - resource.description.toLowerCase().includes(searchTerm.toLowerCase()); + const filteredResources = useMemo(() => { + return externalResources.filter((resource) => { + const matchesSearch = + !searchTerm || + resource.title.toLowerCase().includes(searchTerm.toLowerCase()) || + resource.description.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesCategory = - selectedCategory === "all" || resource.category === selectedCategory; + const matchesCategory = + selectedCategory === "all" || resource.category === selectedCategory; - return matchesSearch && matchesCategory; - }); + return matchesSearch && matchesCategory; + }); + }, [searchTerm, selectedCategory]); const getDifficultyColor = (difficulty) => { switch (difficulty) { diff --git a/src/pages/Forum.jsx b/src/pages/Forum.jsx index 1c15a08..9288318 100644 --- a/src/pages/Forum.jsx +++ b/src/pages/Forum.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -91,16 +91,18 @@ const Forum = () => { } ]; - const filteredPosts = posts?.filter(post => { - const matchesSearch = !searchTerm || - post.title.toLowerCase().includes(searchTerm.toLowerCase()) || - post.content.toLowerCase().includes(searchTerm.toLowerCase()); - - const matchesCategory = selectedCategory === 'all' || - (forums && forums.some(forum => forum.id === post.forum_id && forum.title.toLowerCase().includes(selectedCategory))); - - return matchesSearch && matchesCategory; - }) || []; + const filteredPosts = useMemo(() => { + return posts?.filter(post => { + const matchesSearch = !searchTerm || + post.title.toLowerCase().includes(searchTerm.toLowerCase()) || + post.content.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesCategory = selectedCategory === 'all' || + (forums && forums.some(forum => forum.id === post.forum_id && forum.title.toLowerCase().includes(selectedCategory))); + + return matchesSearch && matchesCategory; + }) || []; + }, [posts, searchTerm, selectedCategory, forums]); const categories = [ { id: 'all', name: 'Semua Diskusi', icon: faComments }, diff --git a/src/pages/Roadmap.jsx b/src/pages/Roadmap.jsx index ebb3a63..7997b6f 100644 --- a/src/pages/Roadmap.jsx +++ b/src/pages/Roadmap.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -62,29 +62,31 @@ const Roadmap = () => { return 'from-gray-500 to-gray-600'; }; - const filteredRoadmaps = roadmaps?.filter(roadmap => { - if (selectedCategory === 'all') return true; - const pathName = roadmap.career_path.toLowerCase(); - - switch (selectedCategory) { - case 'web': - return pathName.includes('web') || pathName.includes('frontend') || pathName.includes('fullstack'); - case 'mobile': - return pathName.includes('mobile'); - case 'data': - return pathName.includes('data') || pathName.includes('scientist'); - case 'devops': - return pathName.includes('devops'); - case 'security': - return pathName.includes('security'); - case 'design': - return pathName.includes('design') || pathName.includes('ui'); - case 'ai': - return pathName.includes('machine') || pathName.includes('ai'); - default: - return true; - } - }) || []; + const filteredRoadmaps = useMemo(() => { + return roadmaps?.filter(roadmap => { + if (selectedCategory === 'all') return true; + const pathName = roadmap.career_path.toLowerCase(); + + switch (selectedCategory) { + case 'web': + return pathName.includes('web') || pathName.includes('frontend') || pathName.includes('fullstack'); + case 'mobile': + return pathName.includes('mobile'); + case 'data': + return pathName.includes('data') || pathName.includes('scientist'); + case 'devops': + return pathName.includes('devops'); + case 'security': + return pathName.includes('security'); + case 'design': + return pathName.includes('design') || pathName.includes('ui'); + case 'ai': + return pathName.includes('machine') || pathName.includes('ai'); + default: + return true; + } + }) || []; + }, [roadmaps, selectedCategory]); if (error) { return (