Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions __tests__/UrlInput_Performance.test.js
Original file line number Diff line number Diff line change
@@ -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 (
<View>
<UrlInput
url="http://example.com"
setUrl={noop}
persist={noop}
onSubmitEditing={noop}
/>
<Text>{unrelated}</Text>
</View>
);
};

act(() => {
create(<Parent />);
});

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);
});
});
14 changes: 8 additions & 6 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useState, useEffect, useRef} from 'react';
import React, {useState, useEffect, useRef, useCallback} from 'react';
import {
Platform,
Text,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -202,9 +206,7 @@ const App = () => {
url={url}
setUrl={setUrl}
persist={persist}
onSubmitEditing={() =>
searchTextInputRef.current && searchTextInputRef.current.focus()
}
onSubmitEditing={handleUrlSubmit}
/>

<SearchInput
Expand Down
2 changes: 1 addition & 1 deletion src/components/UrlInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ const UrlInput = ({url, setUrl, persist, onSubmitEditing}) => {
);
};

export default UrlInput;
export default React.memo(UrlInput);