diff --git a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js index dca154ad170c6..7799756aed853 100644 --- a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js @@ -135,4 +135,36 @@ describe('ProfilerStore', () => { }); }).toThrow('Cannot modify filter preferences while profiling'); }); + + it('should not throw if state contains a property hasOwnProperty ', () => { + let setStateCallback; + const ControlledInput = () => { + const [state, setState] = React.useState({hasOwnProperty: true}); + setStateCallback = setState; + return state.hasOwnProperty; + }; + + const container = document.createElement('div'); + + // This element has to be in the for the event system to work. + document.body.appendChild(container); + + // It's important that this test uses legacy sync mode. + // The root API does not trigger this particular failing case. + ReactDOM.render(, container); + + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => + setStateCallback({ + hasOwnProperty: false, + }), + ); + utils.act(() => store.profilerStore.stopProfiling()); + + // Only one commit should have been recorded (in response to the "change" event). + const root = store.roots[0]; + const data = store.profilerStore.getDataForRoot(root); + expect(data.commitData).toHaveLength(1); + expect(data.operations).toHaveLength(1); + }); }); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 0ef641e410216..fbbac1bcdc6d7 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -1221,15 +1221,18 @@ export function attach( } function isEffect(memoizedState) { + if (memoizedState === null || typeof memoizedState !== 'object') { + return false; + } + const {deps} = memoizedState; + const hasOwnProperty = Object.prototype.hasOwnProperty.bind(memoizedState); return ( - memoizedState !== null && - typeof memoizedState === 'object' && - memoizedState.hasOwnProperty('tag') && - memoizedState.hasOwnProperty('create') && - memoizedState.hasOwnProperty('destroy') && - memoizedState.hasOwnProperty('deps') && - (memoizedState.deps === null || isArray(memoizedState.deps)) && - memoizedState.hasOwnProperty('next') + hasOwnProperty('create') && + hasOwnProperty('destroy') && + hasOwnProperty('deps') && + hasOwnProperty('next') && + hasOwnProperty('tag') && + (deps === null || isArray(deps)) ); }