From 055177f53b0e3c0bc0ee440c5d0dd9509b01db52 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 22:58:30 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Optimize=20SearchInput=20with?= =?UTF-8?q?=20React.memo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrapped `src/components/SearchInput.js` with `React.memo` to prevent unnecessary re-renders. - Refactored `src/App.js` to stabilize the `persist` function by moving it outside the component. - Added a benchmark test `__tests__/SearchInputBenchmark.test.js` to verify the performance improvement (reduced render count from 2 to 1 on unrelated parent updates). Co-authored-by: xRahul <1639945+xRahul@users.noreply.github.com> --- __tests__/SearchInputBenchmark.test.js | 64 ++++++++++++++++++++++++++ src/App.js | 16 +++---- src/components/SearchInput.js | 6 +-- 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 __tests__/SearchInputBenchmark.test.js diff --git a/__tests__/SearchInputBenchmark.test.js b/__tests__/SearchInputBenchmark.test.js new file mode 100644 index 0000000..9731d6b --- /dev/null +++ b/__tests__/SearchInputBenchmark.test.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import renderer, { act } from 'react-test-renderer'; +import SearchInput from '../src/components/SearchInput'; +import { TextInput } from 'react-native'; + +const mockRenderSpy = jest.fn(); + +// Mock react-native to work with react-test-renderer and spy on TextInput +jest.mock('react-native', () => { + const ReactMock = require('react'); + const TextInputMock = ReactMock.forwardRef((props, ref) => { + mockRenderSpy(); + return ReactMock.createElement('TextInput', {...props, ref}); + }); + return { + TextInput: TextInputMock, + }; +}); + +describe('SearchInput Re-render Benchmark', () => { + it('renders SearchInput only once when parent updates with stable props (optimized)', () => { + const persistMock = jest.fn(); + const setSearchTextMock = jest.fn(); + + const Parent = () => { + const [count, setCount] = useState(0); + + // We simulate a stable persist function here to isolate SearchInput's behavior + // independent of App.js implementation for now. + const stablePersist = persistMock; + + return ( + + + setCount(count + 1)} /> + + ); + }; + + const component = renderer.create(); + + // Initial render + expect(mockRenderSpy).toHaveBeenCalledTimes(2); // SearchInput's TextInput + Updater TextInput + + // Update state in Parent + const updater = component.root.findAllByType(TextInput)[1]; // The second one is the updater + + mockRenderSpy.mockClear(); + + act(() => { + updater.props.onChangeText('update'); + }); + + // With React.memo, SearchInput should NOT re-render because props are stable. + // SearchInput NO re-render. + // Updater TextInput re-renders. + // So we expect 1 render. + expect(mockRenderSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/App.js b/src/App.js index a5641d9..666c4ff 100644 --- a/src/App.js +++ b/src/App.js @@ -45,6 +45,14 @@ PushNotification.configure({ requestPermissions: true, }); +const persist = async (key, value) => { + try { + await AsyncStorage.setItem(key, value); + } catch (error) { + console.log(error); + } +}; + const App = () => { const [url, setUrl] = useState(''); const [searchText, setSearchText] = useState(''); @@ -114,14 +122,6 @@ const App = () => { loadState(); }, []); - const persist = async (key, value) => { - try { - await AsyncStorage.setItem(key, value); - } catch (error) { - console.log(error); - } - }; - const createPrefetchJobs = async () => { try { setLoading(true); diff --git a/src/components/SearchInput.js b/src/components/SearchInput.js index 491c8d6..e2a2cb2 100644 --- a/src/components/SearchInput.js +++ b/src/components/SearchInput.js @@ -1,7 +1,7 @@ -import React, {forwardRef} from 'react'; +import React, {forwardRef, memo} from 'react'; import {TextInput} from 'react-native'; -const SearchInput = forwardRef(({searchText, setSearchText, persist}, ref) => { +const SearchInput = memo(forwardRef(({searchText, setSearchText, persist}, ref) => { return ( { @@ -17,6 +17,6 @@ const SearchInput = forwardRef(({searchText, setSearchText, persist}, ref) => { ref={ref} /> ); -}); +})); export default SearchInput; From bf6078957d654149048143da4b5757ae596a15f5 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:24 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Optimize=20SearchInput=20with?= =?UTF-8?q?=20React.memo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrapped `src/components/SearchInput.js` with `React.memo` to prevent unnecessary re-renders. - Refactored `src/App.js` to stabilize the `persist` function by moving it outside the component. - Added a benchmark test `__tests__/SearchInputBenchmark.test.js` to verify the performance improvement (reduced render count from 2 to 1 on unrelated parent updates). - Fixed linting errors caught by CI. Co-authored-by: xRahul <1639945+xRahul@users.noreply.github.com> --- __tests__/SearchInputBenchmark.test.js | 13 ++++++---- src/components/SearchInput.js | 36 ++++++++++++++------------ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/__tests__/SearchInputBenchmark.test.js b/__tests__/SearchInputBenchmark.test.js index 9731d6b..e0db660 100644 --- a/__tests__/SearchInputBenchmark.test.js +++ b/__tests__/SearchInputBenchmark.test.js @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; -import renderer, { act } from 'react-test-renderer'; +import React, {useState} from 'react'; +import renderer, {act} from 'react-test-renderer'; import SearchInput from '../src/components/SearchInput'; -import { TextInput } from 'react-native'; +import {TextInput} from 'react-native'; const mockRenderSpy = jest.fn(); @@ -36,7 +36,10 @@ describe('SearchInput Re-render Benchmark', () => { setSearchText={setSearchTextMock} persist={stablePersist} /> - setCount(count + 1)} /> + setCount(count + 1)} + /> ); }; @@ -52,7 +55,7 @@ describe('SearchInput Re-render Benchmark', () => { mockRenderSpy.mockClear(); act(() => { - updater.props.onChangeText('update'); + updater.props.onChangeText('update'); }); // With React.memo, SearchInput should NOT re-render because props are stable. diff --git a/src/components/SearchInput.js b/src/components/SearchInput.js index e2a2cb2..9dc67b3 100644 --- a/src/components/SearchInput.js +++ b/src/components/SearchInput.js @@ -1,22 +1,24 @@ import React, {forwardRef, memo} from 'react'; import {TextInput} from 'react-native'; -const SearchInput = memo(forwardRef(({searchText, setSearchText, persist}, ref) => { - return ( - { - setSearchText(text); - }} - onEndEditing={e => { - persist('searchText', e.nativeEvent.text); - }} - value={searchText} - autoCorrect={false} - enablesReturnKeyAutomatically - placeholder="Enter Search String" - ref={ref} - /> - ); -})); +const SearchInput = memo( + forwardRef(({searchText, setSearchText, persist}, ref) => { + return ( + { + setSearchText(text); + }} + onEndEditing={e => { + persist('searchText', e.nativeEvent.text); + }} + value={searchText} + autoCorrect={false} + enablesReturnKeyAutomatically + placeholder="Enter Search String" + ref={ref} + /> + ); + }), +); export default SearchInput;