From 75b360458424432c1f69db449bdda7b76d55c4db Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:00:06 +0000 Subject: [PATCH 1/2] Optimize UrlInput re-renders with React.memo and stable callbacks Co-authored-by: xRahul <1639945+xRahul@users.noreply.github.com> --- __tests__/UrlInput_Performance.test.js | 79 ++++++++++++++++++++++++++ src/App.js | 14 +++-- src/components/UrlInput.js | 2 +- 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 __tests__/UrlInput_Performance.test.js diff --git a/__tests__/UrlInput_Performance.test.js b/__tests__/UrlInput_Performance.test.js new file mode 100644 index 0000000..7886aaa --- /dev/null +++ b/__tests__/UrlInput_Performance.test.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { create, act } from 'react-test-renderer'; +import UrlInput from '../src/components/UrlInput'; +import { Text, View } from 'react-native'; + +const mockTextInputRender = jest.fn(); + +// Mock React Native completely to avoid _interopRequireDefault error +jest.mock('react-native', () => { + const React = require('react'); + const TextInput = React.forwardRef((props, ref) => { + mockTextInputRender(props); // Spy on render + return React.createElement('TextInput', {...props, ref}); + }); + + const View = (props) => React.createElement('View', props); + const Text = (props) => React.createElement('Text', props); + + return { + TextInput, + View, + Text, + }; +}); + +describe('UrlInput Performance', () => { + beforeEach(() => { + mockTextInputRender.mockClear(); + }); + + it('renders unnecessarily when parent updates unrelated state', () => { + let setUnrelated; + const Parent = () => { + const [unrelated, setUnrelatedState] = React.useState(0); + setUnrelated = setUnrelatedState; + + // Stable props + const noop = React.useCallback(() => {}, []); + + return ( + + + {unrelated} + + ); + }; + + let root; + act(() => { + root = create(); + }); + + const initialRenderCount = mockTextInputRender.mock.calls.length; + console.log(`Initial Renders: ${initialRenderCount}`); + + // Expect 1 render initially + expect(initialRenderCount).toBe(1); + + act(() => { + setUnrelated(1); + }); + + const finalRenderCount = mockTextInputRender.mock.calls.length; + console.log(`Final Renders: ${finalRenderCount}`); + + // With optimization, finalRenderCount should be 1. + // Without optimization, it should be 2. + // We want this test to FAIL if optimization is missing (to verify baseline). + // So we assert the OPTIMIZED behavior. + + // NOTE: This assertion will fail now (Expected 1, Received 2). + expect(finalRenderCount).toBe(initialRenderCount); + }); +}); diff --git a/src/App.js b/src/App.js index a5641d9..965ee51 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useRef} from 'react'; +import React, {useState, useEffect, useRef, useCallback} from 'react'; import { Platform, Text, @@ -114,13 +114,17 @@ const App = () => { loadState(); }, []); - const persist = async (key, value) => { + const persist = useCallback(async (key, value) => { try { await AsyncStorage.setItem(key, value); } catch (error) { console.log(error); } - }; + }, []); + + const handleUrlSubmit = useCallback(() => { + searchTextInputRef.current && searchTextInputRef.current.focus(); + }, []); const createPrefetchJobs = async () => { try { @@ -202,9 +206,7 @@ const App = () => { url={url} setUrl={setUrl} persist={persist} - onSubmitEditing={() => - searchTextInputRef.current && searchTextInputRef.current.focus() - } + onSubmitEditing={handleUrlSubmit} /> { ); }; -export default UrlInput; +export default React.memo(UrlInput); From b9ed53258123aff4cc2586b1c02d37e30bc331c1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:04:40 +0000 Subject: [PATCH 2/2] Fix lint errors in UrlInput_Performance test Co-authored-by: xRahul <1639945+xRahul@users.noreply.github.com> --- __tests__/UrlInput_Performance.test.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/__tests__/UrlInput_Performance.test.js b/__tests__/UrlInput_Performance.test.js index 7886aaa..17be2ae 100644 --- a/__tests__/UrlInput_Performance.test.js +++ b/__tests__/UrlInput_Performance.test.js @@ -1,25 +1,25 @@ import React from 'react'; -import { create, act } from 'react-test-renderer'; +import {create, act} from 'react-test-renderer'; +import {Text, View} from 'react-native'; import UrlInput from '../src/components/UrlInput'; -import { Text, View } from 'react-native'; const mockTextInputRender = jest.fn(); // Mock React Native completely to avoid _interopRequireDefault error jest.mock('react-native', () => { - const React = require('react'); - const TextInput = React.forwardRef((props, ref) => { + const RNReact = require('react'); + const TextInput = RNReact.forwardRef((props, ref) => { mockTextInputRender(props); // Spy on render - return React.createElement('TextInput', {...props, ref}); + return RNReact.createElement('TextInput', {...props, ref}); }); - const View = (props) => React.createElement('View', props); - const Text = (props) => React.createElement('Text', props); + const RNView = props => RNReact.createElement('View', props); + const RNText = props => RNReact.createElement('Text', props); return { TextInput, - View, - Text, + View: RNView, + Text: RNText, }; }); @@ -50,9 +50,8 @@ describe('UrlInput Performance', () => { ); }; - let root; act(() => { - root = create(); + create(); }); const initialRenderCount = mockTextInputRender.mock.calls.length;