diff --git a/debug-render-analysis.js b/debug-render-analysis.js deleted file mode 100644 index 51c90eec2f6..00000000000 --- a/debug-render-analysis.js +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Render Analysis Debug Script - * - * This script can be pasted into the browser console to help analyze - * component render patterns and detect potential infinite renders. - * - * Usage: - * 1. Open browser DevTools (F12) - * 2. Go to Console tab - * 3. Paste this entire script and press Enter - * 4. Use the application normally - * 5. Call analyzeRenders() to see the analysis - */ - -// Global variables to track renders -window.renderTracker = { - logs: [], - startTime: Date.now(), - componentCounts: {}, - lastRenderTimes: {}, - suspiciousPatterns: [], -}; - -// Override console.log to capture our render logs -const originalConsoleLog = console.log; -console.log = function (...args) { - // Call original console.log first - originalConsoleLog.apply(console, args); - - // Check if this is one of our render logs - const message = args[0]; - if ( - typeof message === "string" && - (message.includes("๐Ÿ”„ GUIComponent RENDER:") || - message.includes("๐Ÿงฑ Blocks") || - message.includes("๐ŸŽญ StageWrapperComponent RENDER:") || - message.includes("๐ŸŽฏ TargetPane")) - ) { - const timestamp = Date.now(); - const logEntry = { - timestamp, - message, - data: args[1] || {}, - component: extractComponentName(message), - }; - - window.renderTracker.logs.push(logEntry); - - // Track component render counts - const component = logEntry.component; - window.renderTracker.componentCounts[component] = - (window.renderTracker.componentCounts[component] || 0) + 1; - - // Check for rapid re-renders (potential infinite renders) - if (window.renderTracker.lastRenderTimes[component]) { - const timeDiff = - timestamp - window.renderTracker.lastRenderTimes[component]; - if (timeDiff < 100) { - // Less than 100ms between renders - window.renderTracker.suspiciousPatterns.push({ - component, - timeDiff, - timestamp, - message: `Rapid re-render detected: ${component} rendered ${timeDiff}ms after previous render`, - }); - } - } - window.renderTracker.lastRenderTimes[component] = timestamp; - } -}; - -function extractComponentName(message) { - if (message.includes("๐Ÿ”„ GUIComponent")) return "GUIComponent"; - if (message.includes("๐Ÿงฑ Blocks")) return "Blocks"; - if (message.includes("๐ŸŽญ StageWrapperComponent")) - return "StageWrapperComponent"; - if (message.includes("๐ŸŽฏ TargetPane")) return "TargetPane"; - return "Unknown"; -} - -// Analysis functions -window.analyzeRenders = function () { - const tracker = window.renderTracker; - const totalTime = Date.now() - tracker.startTime; - - console.group("๐Ÿ” RENDER ANALYSIS REPORT"); - console.log(`๐Ÿ“Š Analysis Period: ${(totalTime / 1000).toFixed(2)} seconds`); - console.log(`๐Ÿ“ Total Render Logs: ${tracker.logs.length}`); - - console.group("๐Ÿ“ˆ Component Render Counts"); - Object.entries(tracker.componentCounts) - .sort(([, a], [, b]) => b - a) - .forEach(([component, count]) => { - const rate = (count / (totalTime / 1000)).toFixed(2); - console.log(`${component}: ${count} renders (${rate} renders/sec)`); - }); - console.groupEnd(); - - if (tracker.suspiciousPatterns.length > 0) { - console.group("โš ๏ธ SUSPICIOUS PATTERNS (Potential Infinite Renders)"); - tracker.suspiciousPatterns.forEach((pattern) => { - console.warn(pattern.message); - }); - console.groupEnd(); - } else { - console.log("โœ… No suspicious rapid re-render patterns detected"); - } - - console.group("๐Ÿ• Recent Renders (Last 10)"); - tracker.logs.slice(-10).forEach((log) => { - const timeFromStart = ( - (log.timestamp - tracker.startTime) / - 1000 - ).toFixed(2); - console.log(`[${timeFromStart}s] ${log.message}`, log.data); - }); - console.groupEnd(); - - console.groupEnd(); - - return { - totalRenders: tracker.logs.length, - componentCounts: tracker.componentCounts, - suspiciousPatterns: tracker.suspiciousPatterns, - analysisTime: totalTime, - }; -}; - -window.clearRenderTracking = function () { - window.renderTracker = { - logs: [], - startTime: Date.now(), - componentCounts: {}, - lastRenderTimes: {}, - suspiciousPatterns: [], - }; - console.log("๐Ÿงน Render tracking data cleared"); -}; - -window.getRendersByComponent = function (componentName) { - return window.renderTracker.logs.filter( - (log) => log.component === componentName - ); -}; - -window.getRecentRenders = function (seconds = 10) { - const cutoff = Date.now() - seconds * 1000; - return window.renderTracker.logs.filter((log) => log.timestamp > cutoff); -}; - -// Utility function to detect useEffect dependency issues -window.detectDependencyIssues = function () { - const recentLogs = window.getRecentRenders(30); - const guiRenders = recentLogs.filter( - (log) => log.component === "GUIComponent" - ); - - console.group("๐Ÿ” DEPENDENCY ANALYSIS"); - - if (guiRenders.length > 5) { - console.warn( - `โš ๏ธ GUIComponent rendered ${guiRenders.length} times in the last 30 seconds` - ); - console.log("This might indicate useEffect dependency issues"); - - // Check for useEffect logs - const useEffectLogs = recentLogs.filter((log) => - log.message.includes("useEffect") - ); - - if (useEffectLogs.length > 0) { - console.group("๐Ÿ”„ Recent useEffect executions:"); - useEffectLogs.forEach((log) => { - console.log(log.message, log.data); - }); - console.groupEnd(); - } - } else { - console.log("โœ… GUIComponent render frequency looks normal"); - } - - console.groupEnd(); -}; - -// Auto-analysis every 30 seconds -let autoAnalysisInterval; -window.startAutoAnalysis = function () { - if (autoAnalysisInterval) { - clearInterval(autoAnalysisInterval); - } - - autoAnalysisInterval = setInterval(() => { - const suspiciousCount = window.renderTracker.suspiciousPatterns.length; - if (suspiciousCount > 0) { - console.warn( - `๐Ÿšจ AUTO-ANALYSIS: ${suspiciousCount} suspicious render patterns detected!` - ); - window.analyzeRenders(); - } - }, 30000); - - console.log("๐Ÿค– Auto-analysis started (runs every 30 seconds)"); -}; - -window.stopAutoAnalysis = function () { - if (autoAnalysisInterval) { - clearInterval(autoAnalysisInterval); - autoAnalysisInterval = null; - console.log("๐Ÿ›‘ Auto-analysis stopped"); - } -}; - -// Initialize -console.log("๐Ÿš€ Render Analysis Debug Script Loaded!"); -console.log("Available functions:"); -console.log(" - analyzeRenders() - Show detailed analysis"); -console.log(" - clearRenderTracking() - Clear tracking data"); -console.log( - " - getRendersByComponent(name) - Get renders for specific component" -); -console.log(" - getRecentRenders(seconds) - Get recent renders"); -console.log(" - detectDependencyIssues() - Check for useEffect issues"); -console.log(" - startAutoAnalysis() - Start automatic monitoring"); -console.log(" - stopAutoAnalysis() - Stop automatic monitoring"); -console.log(""); -console.log( - "๐Ÿ’ก Tip: Use the application normally, then call analyzeRenders() to see the results" -); diff --git a/package.json b/package.json index fd02f1cc2f7..e35fb0723a1 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "default": "./src/index.js" }, "scripts": { - "build": "npm run clean && NODE_ENV=production webpack && cp -r ./build/ ../public/scratch", - "build:dev": "npm run clean && webpack && cp -r ./build/ ../public/scratch", + "build": "npm run clean && webpack && cp -r ./build/ ../public/scratch", "clean": "rimraf ./build ./dist", "deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"[skip ci] Build for $(git log --pretty=format:%H -n1)\"", "prepublish": "node scripts/prepublish.mjs", diff --git a/src/components/controls/controls.jsx b/src/components/controls/controls.jsx index b1b728ae633..59be3a0f9c5 100644 --- a/src/components/controls/controls.jsx +++ b/src/components/controls/controls.jsx @@ -29,7 +29,6 @@ const Controls = function (props) { const { active, className, - costumeURLFax, intl, onGreenFlagClick, onStopAllClick, @@ -124,4 +123,4 @@ const mapStateToProps = (state) => ({ flagClicked: state.scratchGui.vmStatus.flagClicked, }) -export default injectIntl(connect(mapStateToProps, () => ({}))(Controls)) +export default injectIntl(connect(mapStateToProps, null)(Controls)) diff --git a/src/components/customi-cons/reload.jsx b/src/components/customi-cons/reload.jsx index d709ba27e78..89e3f16be31 100644 --- a/src/components/customi-cons/reload.jsx +++ b/src/components/customi-cons/reload.jsx @@ -4,8 +4,8 @@ import styles from './reload.css' function Reload() { return (
- - + +
) diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 3cab4fd00a6..6f531e2925a 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -138,12 +138,6 @@ const GUIComponent = (props) => { onTelemetryModalCancel, onTelemetryModalOptIn, onTelemetryModalOptOut, - setPositionModal, - setProjectName, - setSpriteClickedState, - addNotification, - removeNotification, - setCurrentLayout, showComingSoon, soundsTabVisible, stageSizeMode, @@ -193,10 +187,7 @@ const GUIComponent = (props) => { messenger, methods: { getScratchState(message) { - // Use setTimeout to defer the state update to avoid updating during render - setTimeout(() => { - props.setProjectName(message) - }, 0) + props.setProjectName(message) }, }, timeout: 15000, @@ -219,12 +210,11 @@ const GUIComponent = (props) => { } }, []) - useEffect(() => { if (currentLayout === 'myprojects') { - setPositionModal(true) + props.setPositionModal(true) } - }, [currentLayout, setPositionModal]) + }, [currentLayout]) useEffect(() => { if (remote && currentLayout === 'studentChallenge') { diff --git a/src/components/menu-bar/menu-bar-gui-sub.jsx b/src/components/menu-bar/menu-bar-gui-sub.jsx index afc65695e55..4a2acfb17fa 100644 --- a/src/components/menu-bar/menu-bar-gui-sub.jsx +++ b/src/components/menu-bar/menu-bar-gui-sub.jsx @@ -649,10 +649,6 @@ class MenuBarGuiSub extends React.Component { } } render() { - const { - greenFlagClicked, - ...componentProps - } = this.props const newProjectMessage = ( ) return ( - + {this.props.canManageFiles && (
{ - const { - costumeURLFax, - setCostumeClickedState, - ...componentProps - } = props useEffect(() => { - if (componentProps.costumeURL) { - // Use setTimeout to defer the state update to avoid updating during render - const timeoutId = setTimeout(() => { - setCostumeClickedState(componentProps.costumeURL) - }, 0) - - return () => clearTimeout(timeoutId) - } - }, [setCostumeClickedState, componentProps.costumeURL]) + props.setCostumeClickedState(props.costumeURL) + }, []) return ( { colorInfo, micIndicator, question, - setFlagClickedState, stageSize, useEditorDragStyle, onDeactivateColorPicker, diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 43e0d6062b9..0444e8ababf 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -334,9 +334,7 @@ class Blocks extends React.Component { }) }, 0) } - // REMOVED: this.handleGreenbuttonClick() - This was causing infinite renders - // The handleGreenbuttonClick was updating Redux state on every workspace metric change, - // which triggered re-renders that caused more workspace metric changes. + this.handleGreenbuttonClick() } onScriptGlowOn(data) { this.workspace.glowStack(data.id, true) @@ -576,14 +574,10 @@ class Blocks extends React.Component { /* eslint-disable no-unused-vars */ const { anyModalVisible, - autoSave, canUseCloud, customProceduresVisible, extensionLibraryVisible, - flagClicked, options, - setAutoSaveState, - setFlagClickedState, stageSize, vm, isRtl, diff --git a/src/containers/controls.jsx b/src/containers/controls.jsx index cc094bb6b9d..2b8f095d2db 100644 --- a/src/containers/controls.jsx +++ b/src/containers/controls.jsx @@ -58,9 +58,6 @@ class Controls extends React.Component { isStarted, // eslint-disable-line no-unused-vars projectRunning, turbo, - greenFlagClicked, // eslint-disable-line no-unused-vars - setFlagClickedState, // eslint-disable-line no-unused-vars - setSpriteClickedState, // eslint-disable-line no-unused-vars ...props } = this.props return ( diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 3a055a41650..8b343e6e30c 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -416,7 +416,6 @@ class Stage extends React.Component { const { vm, // eslint-disable-line no-unused-vars onActivateColorPicker, // eslint-disable-line no-unused-vars - setFlagClickedState, // eslint-disable-line no-unused-vars ...props } = this.props return ( diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 4ec38fb03f3..a8cc0307ce5 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -266,7 +266,6 @@ class TargetPane extends React.Component { isRtl, onActivateTab, onCloseImporting, - onGreenFlagClicked, onHighlightTarget, onReceivedBlocks, onShowImporting, diff --git a/src/containers/turbo-mode.jsx b/src/containers/turbo-mode.jsx index bbd1876ea96..d76333749d9 100644 --- a/src/containers/turbo-mode.jsx +++ b/src/containers/turbo-mode.jsx @@ -31,7 +31,6 @@ class TurboMode extends React.Component { const { /* eslint-disable no-unused-vars */ children, - dispatch, vm, /* eslint-enable no-unused-vars */ ...props diff --git a/webpack.config.js b/webpack.config.js index f748a49a789..69f65746c1e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -144,9 +144,6 @@ const baseConfig = new ScratchWebpackConfigBuilder({ ) .addPlugin( new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify( - process.env.NODE_ENV || "development" - ), "process.env.DEBUG": Boolean(process.env.DEBUG), "process.env.GA_ID": `"${process.env.GA_ID || "UA-000000-01"}"`, "process.env.GTM_ENV_AUTH": `"${process.env.GTM_ENV_AUTH || ""}"`, @@ -155,12 +152,6 @@ const baseConfig = new ScratchWebpackConfigBuilder({ : null, }) ) - .addPlugin( - new webpack.NormalModuleReplacementPlugin( - /^redux$/, - require.resolve("redux/dist/redux.min.js") - ) - ) .addPlugin( new CopyWebpackPlugin({ patterns: [