diff --git a/__tests__/UrlInput_Performance.test.js b/__tests__/UrlInput_Performance.test.js
new file mode 100644
index 0000000..17be2ae
--- /dev/null
+++ b/__tests__/UrlInput_Performance.test.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import {create, act} from 'react-test-renderer';
+import {Text, View} from 'react-native';
+import UrlInput from '../src/components/UrlInput';
+
+const mockTextInputRender = jest.fn();
+
+// Mock React Native completely to avoid _interopRequireDefault error
+jest.mock('react-native', () => {
+ const RNReact = require('react');
+ const TextInput = RNReact.forwardRef((props, ref) => {
+ mockTextInputRender(props); // Spy on render
+ return RNReact.createElement('TextInput', {...props, ref});
+ });
+
+ const RNView = props => RNReact.createElement('View', props);
+ const RNText = props => RNReact.createElement('Text', props);
+
+ return {
+ TextInput,
+ View: RNView,
+ Text: RNText,
+ };
+});
+
+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}
+
+ );
+ };
+
+ act(() => {
+ 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);