diff --git a/src/Expensify.js b/src/Expensify.js
index 797363e7ebdc..5277d4a3e7dc 100644
--- a/src/Expensify.js
+++ b/src/Expensify.js
@@ -1,5 +1,6 @@
import React, {Component} from 'react';
import {View} from 'react-native';
+import PropTypes from 'prop-types';
// import {Beforeunload} from 'react-beforeunload';
import SignInPage from './page/SignInPage';
@@ -21,6 +22,17 @@ import {
// Initialize the store when the app loads for the first time
Ion.init();
+const propTypes = {
+ /* Ion Props */
+
+ // A route set by Ion that we will redirect to if present. Always empty on app init.
+ redirectTo: PropTypes.string,
+};
+
+const defaultProps = {
+ redirectTo: '',
+};
+
class Expensify extends Component {
constructor(props) {
super(props);
@@ -62,7 +74,7 @@ class Expensify extends Component {
// We can only have a redirectTo if this is not the initial render so if we have one we'll
// always navigate to it. If we are not authenticated by this point then we'll force navigate to sign in.
- const redirectTo = this.state.redirectTo || (!this.state.authToken && '/signin');
+ const redirectTo = this.props.redirectTo || (!this.state.authToken && '/signin');
return (
@@ -84,6 +96,9 @@ class Expensify extends Component {
}
}
+Expensify.propTypes = propTypes;
+Expensify.defaultProps = defaultProps;
+
export default WithIon({
redirectTo: {
key: IONKEYS.APP_REDIRECT_TO,
diff --git a/src/components/WithIon.js b/src/components/WithIon.js
index 9da95d77c5c7..0e1ec3bbe29f 100644
--- a/src/components/WithIon.js
+++ b/src/components/WithIon.js
@@ -92,13 +92,12 @@ export default function (mapIonToState) {
* @param {boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
* component
* @param {string} statePropertyName the name of the state property that Ion will add the data to
- * @param {object} reactComponent a reference to the react component whose state needs updated by Ion
*/
- connectMappingToIon(mapping, statePropertyName, reactComponent) {
+ connectMappingToIon(mapping, statePropertyName) {
const ionConnectionConfig = {
...mapping,
statePropertyName,
- reactComponent,
+ withIonInstance: this,
};
// Connect to Ion and keep track of the connectionID
@@ -120,7 +119,7 @@ export default function (mapIonToState) {
// Pre-fill the state with any data already in the store
if (mapping.initWithStoredValues !== false) {
Ion.get(ionConnectionConfig.key, mapping.path, mapping.defaultValue)
- .then(data => reactComponent.setState({[statePropertyName]: data}));
+ .then(data => this.setState({[statePropertyName]: data}));
}
// Load the data from an API request if necessary
diff --git a/src/lib/Ion.js b/src/lib/Ion.js
index f64c0e7f58b6..03c84e41a815 100644
--- a/src/lib/Ion.js
+++ b/src/lib/Ion.js
@@ -33,7 +33,7 @@ const callbackToStateMapping = {};
* @param {string} mapping.statePropertyName the name of the property in the state to connect the data to
* @param {boolean} [mapping.addAsCollection] rather than setting a single state value, this will add things to an array
* @param {string} [mapping.collectionID] the name of the ID property to use for the collection
- * @param {object} mapping.reactComponent whose setState() method will be called with any changed data
+ * @param {object} mapping.withIonInstance whose setState() method will be called with any changed data
* @returns {number} an ID to use when calling disconnect
*/
function connect(mapping) {
@@ -75,7 +75,7 @@ function keyChanged(key, data) {
// Set the state of the react component with either the pathed data, or the data
if (mappedComponent.addAsCollection) {
// Add the data to an array of existing items
- mappedComponent.reactComponent.setState((prevState) => {
+ mappedComponent.withIonInstance.setState((prevState) => {
const collection = prevState[mappedComponent.statePropertyName] || {};
collection[newValue[mappedComponent.collectionID]] = newValue;
const newState = {
@@ -84,7 +84,7 @@ function keyChanged(key, data) {
return newState;
});
} else {
- mappedComponent.reactComponent.setState({
+ mappedComponent.withIonInstance.setState({
[mappedComponent.statePropertyName]: newValue,
});
}
diff --git a/src/page/HomePage/HeaderView.js b/src/page/HomePage/HeaderView.js
index bf33a7f30bd6..9d6b93bf02a4 100644
--- a/src/page/HomePage/HeaderView.js
+++ b/src/page/HomePage/HeaderView.js
@@ -14,39 +14,47 @@ const propTypes = {
// Decides whether we should show the hamburger menu button
shouldShowHamburgerButton: PropTypes.bool.isRequired,
+
+ /* Ion Props */
+
+ // Name of the report (if we have one)
+ reportName: PropTypes.string,
};
-class HeaderView extends React.Component {
- render() {
- return (
-
-
- {this.props.shouldShowHamburgerButton && (
-
-
-
- )}
- {this.state && this.state.reportName && (
-
- {this.state.reportName}
-
- )}
-
-
- );
- }
-}
+const defaultProps = {
+ reportName: '',
+};
+
+const HeaderView = props => (
+
+
+ {props.shouldShowHamburgerButton && (
+
+
+
+ )}
+ {props.reportName && (
+
+ {props.reportName}
+
+ )}
+
+
+);
+
HeaderView.propTypes = propTypes;
+HeaderView.displayName = 'HeaderView';
+HeaderView.defaultProps = defaultProps;
export default withRouter(WithIon({
- // Map this.state.reportName to the data for a specific report in the store, and bind it to the reportName property
+ // Map this.props.reportName to the data for a specific report in the store, and bind it to the reportName property
// It uses the data returned from the props path (ie. the reportID) to replace %DATAFROMPROPS% in the key it
// binds to
reportName: {
diff --git a/src/page/HomePage/MainView.js b/src/page/HomePage/MainView.js
index 07e638a18592..c8007386692d 100644
--- a/src/page/HomePage/MainView.js
+++ b/src/page/HomePage/MainView.js
@@ -12,17 +12,22 @@ const propTypes = {
// This comes from withRouter
// eslint-disable-next-line react/forbid-prop-types
match: PropTypes.object.isRequired,
-};
-class MainView extends React.Component {
- constructor(props) {
- super(props);
+ /* Ion Props */
- this.state = {};
- }
+ // List of reports to display
+ reports: PropTypes.arrayOf(PropTypes.shape({
+ reportID: PropTypes.number,
+ })),
+};
+const defaultProps = {
+ reports: [],
+};
+
+class MainView extends React.Component {
render() {
- if (!this.state || !this.state.reports || this.state.reports.length === 0) {
+ if (this.props.reports.length === 0) {
return null;
}
@@ -30,7 +35,7 @@ class MainView extends React.Component {
// The styles for each of our reports. Basically, they are all hidden except for the one matching the
// reportID in the URL
- const reportStyles = _.reduce(this.state.reports, (memo, report) => {
+ const reportStyles = _.reduce(this.props.reports, (memo, report) => {
const finalData = {...memo};
const reportStyle = reportIDInURL === report.reportID
? [styles.dFlex, styles.flex1]
@@ -41,7 +46,7 @@ class MainView extends React.Component {
return (
<>
- {_.map(this.state.reports, report => (
+ {_.map(this.props.reports, report => (
Be the first person to comment!
@@ -137,11 +146,11 @@ class ReportHistoryView extends React.Component {
paddingVertical: 8
}}
>
- {_.chain(reportHistory).sortBy('sequenceNumber').map((item, index) => (
+ {_.chain(this.props.reportHistory).sortBy('sequenceNumber').map((item, index) => (
)).value()}
@@ -149,7 +158,9 @@ class ReportHistoryView extends React.Component {
);
}
}
+
ReportHistoryView.propTypes = propTypes;
+ReportHistoryView.defaultProps = defaultProps;
const key = `${IONKEYS.REPORT_HISTORY}_%DATAFROMPROPS%`;
export default withRouter(WithIon({
diff --git a/src/page/HomePage/SidebarLink.js b/src/page/HomePage/SidebarLink.js
index c46f3256c33f..7af619445c31 100644
--- a/src/page/HomePage/SidebarLink.js
+++ b/src/page/HomePage/SidebarLink.js
@@ -21,31 +21,41 @@ const propTypes = {
// Toggles the hamburger menu open and closed
onLinkClick: PropTypes.func.isRequired,
+
+ /* Ion Props */
+
+ // Does the report for this link have unread comments?
+ isUnread: PropTypes.bool,
};
-class SidebarLink extends React.Component {
- render() {
- const paramsReportID = parseInt(this.props.match.params.reportID, 10);
- const isReportActive = paramsReportID === this.props.reportID;
- const linkWrapperActiveStyle = isReportActive && styles.sidebarLinkWrapperActive;
- const linkActiveStyle = isReportActive ? styles.sidebarLinkActive : styles.sidebarLink;
- const textActiveStyle = isReportActive ? styles.sidebarLinkActiveText : styles.sidebarLinkText;
- const textActiveUnreadStyle = this.state && this.state.isUnread
- ? [textActiveStyle, styles.sidebarLinkTextUnread] : [textActiveStyle];
- return (
-
-
-
-
- {this.props.reportName}
-
-
-
-
- );
- }
-}
+const defaultProps = {
+ isUnread: false,
+};
+
+const SidebarLink = (props) => {
+ const paramsReportID = parseInt(props.match.params.reportID, 10);
+ const isReportActive = paramsReportID === props.reportID;
+ const linkWrapperActiveStyle = isReportActive && styles.sidebarLinkWrapperActive;
+ const linkActiveStyle = isReportActive ? styles.sidebarLinkActive : styles.sidebarLink;
+ const textActiveStyle = isReportActive ? styles.sidebarLinkActiveText : styles.sidebarLinkText;
+ const textActiveUnreadStyle = props.isUnread
+ ? [textActiveStyle, styles.sidebarLinkTextUnread] : [textActiveStyle];
+ return (
+
+
+
+
+ {props.reportName}
+
+
+
+
+ );
+};
+
+SidebarLink.displayName = 'SidebarLink';
SidebarLink.propTypes = propTypes;
+SidebarLink.defaultProps = defaultProps;
export default withRouter(WithIon({
isUnread: {
diff --git a/src/page/HomePage/SidebarView.js b/src/page/HomePage/SidebarView.js
index 71cd0b9a5800..dd68c26d42a8 100644
--- a/src/page/HomePage/SidebarView.js
+++ b/src/page/HomePage/SidebarView.js
@@ -25,7 +25,32 @@ const propTypes = {
// Safe area insets required for mobile devices margins
// eslint-disable-next-line react/forbid-prop-types
- insets: PropTypes.object.isRequired
+ insets: PropTypes.object.isRequired,
+
+ /* Ion Props */
+
+ // Display name of the current user from their personal details
+ userDisplayName: PropTypes.string,
+
+ // Avatar URL of the current user from their personal details
+ avatarURL: PropTypes.string,
+
+ // List of reports
+ reports: PropTypes.arrayOf(PropTypes.shape({
+ hasUnread: PropTypes.bool,
+ reportName: PropTypes.string,
+ reportID: PropTypes.number,
+ })),
+
+ // Is this person offline?
+ isOffline: PropTypes.bool,
+};
+
+const defaultProps = {
+ userDisplayName: '',
+ avatarURL: '',
+ reports: [],
+ isOffline: false,
};
class SidebarView extends React.Component {
@@ -33,10 +58,8 @@ class SidebarView extends React.Component {
* Updates the page title to indicate there are unread reports
*/
updateUnreadReportIndicator() {
- if (this.state) {
- const hasUnreadReports = _.any(this.state.reports, report => report.hasUnread);
- PageTitleUpdater(hasUnreadReports);
- }
+ const hasUnreadReports = _.any(this.props.reports, report => report.hasUnread);
+ PageTitleUpdater(hasUnreadReports);
}
alertInstallInstructions() {
@@ -51,7 +74,7 @@ class SidebarView extends React.Component {
}
render() {
- const reports = this.state && this.state.reports;
+ const reports = this.props.reports;
this.updateUnreadReportIndicator();
return (
@@ -80,17 +103,17 @@ class SidebarView extends React.Component {
- {this.state && this.state.isOffline && (
+ {this.props.isOffline && (
)}
- {this.state && this.state.userDisplayName && (
+ {this.props.userDisplayName && (
- {this.state.userDisplayName}
+ {this.props.userDisplayName}
)}
@@ -122,9 +145,10 @@ class SidebarView extends React.Component {
}
SidebarView.propTypes = propTypes;
+SidebarView.defaultProps = defaultProps;
export default WithIon({
- // Map this.state.userDisplayName to the personal details key in the store and bind it to the displayName property
+ // Map this.props.userDisplayName to the personal details key in the store and bind it to the displayName property
// and load it with data from getPersonalDetails()
userDisplayName: {
key: IONKEYS.MY_PERSONAL_DETAILS,
diff --git a/src/page/SignInPage.js b/src/page/SignInPage.js
index 19d45a0d66b9..52714cfc9ca2 100644
--- a/src/page/SignInPage.js
+++ b/src/page/SignInPage.js
@@ -20,6 +20,15 @@ const propTypes = {
// These are from withRouter
// eslint-disable-next-line react/forbid-prop-types
match: PropTypes.object.isRequired,
+
+ /* Ion Props */
+
+ // Error to display when there is a session error returned
+ error: PropTypes.string,
+};
+
+const defaultProps = {
+ error: '',
};
class App extends Component {
@@ -103,9 +112,9 @@ class App extends Component {
>
Log In
- {this.state.error && (
+ {this.props.error && (
- {this.state.error}
+ {this.props.error}
)}
@@ -117,8 +126,9 @@ class App extends Component {
}
App.propTypes = propTypes;
+App.defaultProps = defaultProps;
export default withRouter(WithIon({
- // Bind this.state.error to the error in the session object
+ // Bind this.props.error to the error in the session object
error: {key: IONKEYS.SESSION, path: 'error', defaultValue: null},
})(App));