From c1130a167b27ab6b51441c2ceef33fddb555cbb2 Mon Sep 17 00:00:00 2001 From: Chaiwat Ekkaewnumchai Date: Mon, 4 Nov 2019 07:42:39 -0800 Subject: [PATCH] Upgrade React-Redux (2nd attempt) Summary: - Update and include `react-redux` to Greenkeeper - Remove legacy context which is used to access `store` - Export `store` directly to use instead of legacy context Note: 1st attempt: D18169584 -> got backouted in D18203354 Reviewed By: jknoxville Differential Revision: D18297298 fbshipit-source-id: fd968f1b211eabb094113a0b35e84305f70117fc --- package.json | 6 +- src/NotificationsHub.tsx | 71 +++++++------- src/chrome/DevicesButton.tsx | 33 +++---- src/chrome/ListView.tsx | 89 ++++++++--------- src/chrome/ShareSheetExportFile.tsx | 142 ++++++++++++++-------------- src/chrome/ShareSheetExportUrl.tsx | 141 ++++++++++++++------------- src/init.tsx | 2 +- yarn.lock | 33 ++++--- 8 files changed, 264 insertions(+), 253 deletions(-) diff --git a/package.json b/package.json index ba9355fe0..25526a58f 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/lodash.isequal": "^4.5.5", "@types/react": "16.9.11", "@types/react-dom": "^16.9.2", - "@types/react-redux": "^7.1.1", + "@types/react-redux": "^7.1.5", "@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-window": "^1.8.1", "@types/redux-persist": "^4.3.1", @@ -155,7 +155,7 @@ "react-devtools-core": "^4.0.6", "react-dom": "^16.11.0", "react-emotion": "^9.2.6", - "react-redux": "^5.0.7", + "react-redux": "^7.1.1", "react-test-renderer": "^16.11.0", "react-transition-group": "^4.3.0", "react-virtualized-auto-sizer": "^1.0.2", @@ -179,12 +179,10 @@ }, "greenkeeper": { "ignore": [ - "@types/react-redux", "electron", "electron-builder", "emotion", "react-emotion", - "react-redux", "tmp" ] }, diff --git a/src/NotificationsHub.tsx b/src/NotificationsHub.tsx index 8e8646a0e..76d4415cf 100644 --- a/src/NotificationsHub.tsx +++ b/src/NotificationsHub.tsx @@ -22,10 +22,10 @@ import { colors, } from 'flipper'; import {FlipperDevicePlugin, BaseAction} from './plugin'; -import {connect} from 'react-redux'; +import {connect, ReactReduxContext} from 'react-redux'; +import {store} from './init'; import React, {Component, Fragment} from 'react'; import {clipboard} from 'electron'; -import PropTypes from 'prop-types'; import { PluginNotification, clearAllNotifications, @@ -49,51 +49,54 @@ export default class Notifications< static icon = 'bell'; static keyboardActions: KeyboardActions = ['clear']; - static contextTypes = { - store: PropTypes.object.isRequired, - }; - static supportsDevice() { return false; } onKeyboardAction = (action: string) => { if (action === 'clear') { - this.onClear(); + this.onClear(store)(); } }; - onClear = () => { - (this.context.store as Store).dispatch(clearAllNotifications()); + onClear = (store: Store) => () => { + store.dispatch(clearAllNotifications()); }; render() { - const {blacklistedPlugins, blacklistedCategories} = (this.context - .store as Store).getState().notifications; return ( - ({ - value, - type: 'exclude', - key: 'plugin', - })), - ...blacklistedCategories.map(value => ({ - value, - type: 'exclude', - key: 'category', - })), - ]} - actions={ - - - - } - /> + + {({store}) => { + const {blacklistedPlugins, blacklistedCategories} = (store as Store< + StoreState + >).getState().notifications; + return ( + ({ + value, + type: 'exclude', + key: 'plugin', + })), + ...blacklistedCategories.map(value => ({ + value, + type: 'exclude', + key: 'category', + })), + ]} + actions={ + + + + } + /> + ); + }} + ); } } diff --git a/src/chrome/DevicesButton.tsx b/src/chrome/DevicesButton.tsx index 75594ed80..cc4b0c0a6 100644 --- a/src/chrome/DevicesButton.tsx +++ b/src/chrome/DevicesButton.tsx @@ -8,13 +8,12 @@ */ import {Button, styled} from 'flipper'; -import {connect} from 'react-redux'; +import {connect, ReactReduxContext} from 'react-redux'; import {spawn} from 'child_process'; import {dirname} from 'path'; import {selectDevice, preferDevice} from '../reducers/connections'; import {default as which} from 'which'; import {showOpenDialog} from '../utils/exportData'; -import PropTypes from 'prop-types'; import BaseDevice from '../devices/BaseDevice'; import React, {Component} from 'react'; import {State} from '../reducers'; @@ -38,10 +37,6 @@ const DropdownButton = styled(Button)({ }); class DevicesButton extends Component { - static contextTypes = { - store: PropTypes.object.isRequired, - }; - launchEmulator = (name: string) => { // On Linux, you must run the emulator from the directory it's in because // reasons ... @@ -85,7 +80,7 @@ class DevicesButton extends Component { icon = 'desktop'; } - const dropdown = []; + const dropdown: any[] = []; // Physical devices const connectedDevices = [ @@ -169,16 +164,22 @@ class DevicesButton extends Component { if (dropdown.length > 0) { dropdown.push({type: 'separator' as 'separator'}); } - dropdown.push({ - label: 'Open File...', - click: () => { - showOpenDialog(this.context.store); - }, - }); return ( - - {buttonLabel} - + + {({store}) => { + dropdown.push({ + label: 'Open File...', + click: () => { + showOpenDialog(store); + }, + }); + return ( + + {buttonLabel} + + ); + }} + ); } } diff --git a/src/chrome/ListView.tsx b/src/chrome/ListView.tsx index d4532a6d1..ac9e5ec47 100644 --- a/src/chrome/ListView.tsx +++ b/src/chrome/ListView.tsx @@ -20,7 +20,7 @@ import { } from 'flipper'; import {unsetShare} from '../reducers/application'; import React, {Component} from 'react'; -import PropTypes from 'prop-types'; +import {ReactReduxContext} from 'react-redux'; export type SelectionType = 'multiple' | 'single'; @@ -122,10 +122,6 @@ class RowComponent extends Component { } export default class ListView extends Component { - static contextTypes = { - store: PropTypes.object.isRequired, - }; - state: State = {selectedElements: new Set([])}; static getDerivedStateFromProps(props: Props, state: State) { if (state.selectedElements.size > 0) { @@ -161,46 +157,51 @@ export default class ListView extends Component { }; render() { - const onHide = () => { - this.context.store.dispatch(unsetShare()); - this.props.onHide(); - }; - return ( - - - {this.props.title} - - {this.props.elements.map(id => { - return ( - - ); - })} - - - - - - - - - - + + {({store}) => { + const onHide = () => { + store.dispatch(unsetShare()); + this.props.onHide(); + }; + return ( + + + {this.props.title} + + {this.props.elements.map(id => { + return ( + + ); + })} + + + + + + + + + + + ); + }} + ); } } diff --git a/src/chrome/ShareSheetExportFile.tsx b/src/chrome/ShareSheetExportFile.tsx index 736b81ae2..9bff10fa5 100644 --- a/src/chrome/ShareSheetExportFile.tsx +++ b/src/chrome/ShareSheetExportFile.tsx @@ -19,9 +19,10 @@ import { exportStoreToFile, EXPORT_FLIPPER_TRACE_EVENT, } from '../utils/exportData'; -import PropTypes from 'prop-types'; import ShareSheetErrorList from './ShareSheetErrorList'; import ShareSheetPendingDialog from './ShareSheetPendingDialog'; +import {ReactReduxContext} from 'react-redux'; +import {store} from '../init'; const Container = styled(FlexColumn)({ padding: 20, @@ -75,10 +76,6 @@ type State = { }; export default class ShareSheetExportFile extends Component { - static contextTypes = { - store: PropTypes.object.isRequired, - }; - state: State = { errorArray: [], result: {kind: 'pending'}, @@ -89,13 +86,13 @@ export default class ShareSheetExportFile extends Component { idler = new Idler(); dispatchAndUpdateToolBarStatus(msg: string) { - this.context.store.dispatch( + store.dispatch( setExportStatusComponent( { this.idler.cancel(); - this.context.store.dispatch(unsetShare()); + store.dispatch(unsetShare()); }} />, ), @@ -110,21 +107,16 @@ export default class ShareSheetExportFile extends Component { return; } const {errorArray} = await reportPlatformFailures( - exportStoreToFile( - this.props.file, - this.context.store, - this.idler, - (msg: string) => { - if (this.state.runInBackground) { - this.dispatchAndUpdateToolBarStatus(msg); - } else { - this.setState({statusUpdate: msg}); - } - }, - ), + exportStoreToFile(this.props.file, store, this.idler, (msg: string) => { + if (this.state.runInBackground) { + this.dispatchAndUpdateToolBarStatus(msg); + } else { + this.setState({statusUpdate: msg}); + } + }), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`, ); - this.context.store.dispatch(unsetShare()); + store.dispatch(unsetShare()); if (this.state.runInBackground) { new Notification('Sharable Flipper trace created', { body: `Flipper trace exported to the ${this.props.file}`, @@ -142,64 +134,76 @@ export default class ShareSheetExportFile extends Component { } } - renderSuccess(context: any) { + renderSuccess() { return ( - - - Data Exported Successfully - - When sharing your Flipper data, consider that the captured data - might contain sensitive information like access tokens used in - network requests. - - - - - - - - + + {({store}) => ( + + + Data Exported Successfully + + When sharing your Flipper data, consider that the captured data + might contain sensitive information like access tokens used in + network requests. + + + + + + + + + )} + ); } - renderError(context: any, result: {kind: 'error'; error: Error}) { + renderError(result: {kind: 'error'; error: Error}) { return ( - - Error - - {result.error.message || 'File could not be saved.'} - - - - - - + + {({store}) => ( + + Error + + {result.error.message || 'File could not be saved.'} + + + + + + + )} + ); } - renderPending(context: any, statusUpdate: string | null) { + renderPending(statusUpdate: string | null) { return ( - this.cancelAndHide(context)} - onRunInBackground={() => { - this.setState({runInBackground: true}); - if (statusUpdate) { - this.dispatchAndUpdateToolBarStatus(statusUpdate); - } - this.props.onHide(); - }} - /> + + {({store}) => ( + this.cancelAndHide(store)} + onRunInBackground={() => { + this.setState({runInBackground: true}); + if (statusUpdate) { + this.dispatchAndUpdateToolBarStatus(statusUpdate); + } + this.props.onHide(); + }} + /> + )} + ); } - cancelAndHide(context: any) { - context.store.dispatch(unsetShare()); + cancelAndHide(store: any) { + store.dispatch(unsetShare()); this.props.onHide(); this.idler.cancel(); } @@ -208,11 +212,11 @@ export default class ShareSheetExportFile extends Component { const {result, statusUpdate} = this.state; switch (result.kind) { case 'success': - return this.renderSuccess(this.context); + return this.renderSuccess(); case 'error': - return this.renderError(this.context, result); + return this.renderError(result); case 'pending': - return this.renderPending(this.context, statusUpdate); + return this.renderPending(statusUpdate); } } } diff --git a/src/chrome/ShareSheetExportUrl.tsx b/src/chrome/ShareSheetExportUrl.tsx index a499b09dd..906a08f79 100644 --- a/src/chrome/ShareSheetExportUrl.tsx +++ b/src/chrome/ShareSheetExportUrl.tsx @@ -17,6 +17,8 @@ import { Input, } from 'flipper'; import React, {Component} from 'react'; +import {ReactReduxContext} from 'react-redux'; +import {store} from '../init'; import { setExportStatusComponent, unsetShare, @@ -30,7 +32,6 @@ import { DataExportError, } from '../fb-stubs/user'; import {exportStore, EXPORT_FLIPPER_TRACE_EVENT} from '../utils/exportData'; -import PropTypes from 'prop-types'; import {clipboard} from 'electron'; import ShareSheetErrorList from './ShareSheetErrorList'; import {reportPlatformFailures} from '../utils/metrics'; @@ -80,10 +81,6 @@ type State = { }; export default class ShareSheetExportUrl extends Component { - static contextTypes = { - store: PropTypes.object.isRequired, - }; - state: State = { errorArray: [], result: null, @@ -94,13 +91,13 @@ export default class ShareSheetExportUrl extends Component { idler = new Idler(); dispatchAndUpdateToolBarStatus(msg: string) { - this.context.store.dispatch( + store.dispatch( setExportStatusComponent( { this.idler.cancel(); - this.context.store.dispatch(unsetShare()); + store.dispatch(unsetShare()); }} />, ), @@ -119,7 +116,7 @@ export default class ShareSheetExportUrl extends Component { } }; const {serializedString, errorArray} = await reportPlatformFailures( - exportStore(this.context.store, this.idler, statusUpdate), + exportStore(store, this.idler, statusUpdate), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_LINK`, ); @@ -135,7 +132,7 @@ export default class ShareSheetExportUrl extends Component { const flipperUrl = (result as DataExportResult).flipperUrl; if (flipperUrl) { clipboard.writeText(String(flipperUrl)); - this.context.store.dispatch(setExportURL(flipperUrl)); + store.dispatch(setExportURL(flipperUrl)); new Notification('Sharable Flipper trace created', { body: 'URL copied to clipboard', requireInteraction: true, @@ -158,7 +155,7 @@ export default class ShareSheetExportUrl extends Component { } this.setState({result}); } - this.context.store.dispatch(unsetShare()); + store.dispatch(unsetShare()); this.props.logger.trackTimeSince(mark, 'export:url-error'); } } @@ -181,74 +178,82 @@ export default class ShareSheetExportUrl extends Component { } } - renderPending(cancelAndHide: () => void, statusUpdate: string | null) { + cancelAndHide = (store: any) => () => { + store.dispatch(unsetShare()); + this.hideSheet(); + }; + + renderPending(statusUpdate: string | null) { return ( - { - this.setState({runInBackground: true}); - if (statusUpdate) { - this.dispatchAndUpdateToolBarStatus(statusUpdate); - } - this.props.onHide(); - }} - /> + + {({store}) => ( + { + this.setState({runInBackground: true}); + if (statusUpdate) { + this.dispatchAndUpdateToolBarStatus(statusUpdate); + } + this.props.onHide(); + }} + /> + )} + ); } render() { - const cancelAndHide = () => { - this.context.store.dispatch(unsetShare()); - this.hideSheet(); - }; - const {result, statusUpdate, errorArray} = this.state; if (!result || !(result as DataExportResult).flipperUrl) { - return this.renderPending(cancelAndHide, statusUpdate); + return this.renderPending(statusUpdate); } return ( - - <> - - {(result as DataExportResult).flipperUrl ? ( - <> - Data Upload Successful - - Flipper's data was successfully uploaded. This URL can be used - to share with other Flipper users. Opening it will import the - data from your trace. - - - - When sharing your Flipper link, consider that the captured - data might contain sensitve information like access tokens - used in network requests. - - - - ) : ( - <> - - {(result as DataExportError).error_class || 'Error'} - - - {(result as DataExportError).error || - 'The data could not be uploaded'} - - - )} - - - - - - - + + {({store}) => ( + + <> + + {(result as DataExportResult).flipperUrl ? ( + <> + Data Upload Successful + + Flipper's data was successfully uploaded. This URL can be + used to share with other Flipper users. Opening it will + import the data from your trace. + + + + When sharing your Flipper link, consider that the captured + data might contain sensitve information like access tokens + used in network requests. + + + + ) : ( + <> + + {(result as DataExportError).error_class || 'Error'} + + + {(result as DataExportError).error || + 'The data could not be uploaded'} + + + )} + + + + + + + + )} + ); } } diff --git a/src/init.tsx b/src/init.tsx index 5913263c2..040c915d7 100644 --- a/src/init.tsx +++ b/src/init.tsx @@ -32,7 +32,7 @@ import {setPersistor} from './utils/persistor'; import React from 'react'; import path from 'path'; -const store = createStore( +export const store = createStore( reducers, window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__({ diff --git a/yarn.lock b/yarn.lock index 91a2d5f6b..e4bea3173 100644 --- a/yarn.lock +++ b/yarn.lock @@ -702,7 +702,7 @@ pirates "^4.0.0" source-map-support "^0.5.9" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.5", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3": version "7.6.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f" integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA== @@ -1265,7 +1265,7 @@ dependencies: "@types/react" "*" -"@types/react-redux@^7.1.1": +"@types/react-redux@^7.1.5": version "7.1.5" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.5.tgz#c7a528d538969250347aa53c52241051cf886bd3" integrity sha512-ZoNGQMDxh5ENY7PzU7MVonxDzS1l/EWiy8nUhDqxFqUZn4ovboCyvk4Djf68x6COb7vhGTKjyjxHxtFdAA5sUA== @@ -6685,7 +6685,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -6830,28 +6830,27 @@ react-emotion@^9.2.6: babel-plugin-emotion "^9.2.11" create-emotion-styled "^9.2.8" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.10.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab" integrity sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA== -react-lifecycles-compat@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-is@^16.9.0: + version "16.11.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" + integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== -react-redux@^5.0.7: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57" - integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q== +react-redux@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.1.tgz#ce6eee1b734a7a76e0788b3309bf78ff6b34fa0a" + integrity sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg== dependencies: - "@babel/runtime" "^7.1.2" + "@babel/runtime" "^7.5.5" hoist-non-react-statics "^3.3.0" invariant "^2.2.4" - loose-envify "^1.1.0" - prop-types "^15.6.1" - react-is "^16.6.0" - react-lifecycles-compat "^3.0.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" react-test-renderer@^16.11.0: version "16.11.0"