Skip to content

Commit 2a491c8

Browse files
authored
ref(ts): Convert higher order components to TypeScript (#14326)
1 parent eff69db commit 2a491c8

20 files changed

+129
-69
lines changed

src/sentry/static/sentry/app/types/index.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,23 @@ export type CommitAuthor = {
6464
email?: string;
6565
name?: string;
6666
};
67+
68+
// TODO(ts): This type is incomplete
69+
export type Environment = {};
70+
71+
// TODO(ts): This type is incomplete
72+
export type SavedSearch = {};
73+
74+
// TODO(ts): This type is incomplete
75+
export type Plugin = {};
76+
77+
export type GlobalSelection = {
78+
projects: number[];
79+
environments: string[];
80+
datetime: {
81+
start: string;
82+
end: string;
83+
period: string;
84+
utc: boolean;
85+
};
86+
};

src/sentry/static/sentry/app/utils/__mocks__/withLatestContext.jsx renamed to src/sentry/static/sentry/app/utils/__mocks__/withLatestContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22

3+
declare const TestStubs;
4+
35
const MOCK_ORG = TestStubs.Organization();
46
const DEFAULTS = {
57
organization: MOCK_ORG,

src/sentry/static/sentry/app/utils/__mocks__/withOrganization.jsx renamed to src/sentry/static/sentry/app/utils/__mocks__/withOrganization.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react';
22

33
import SentryTypes from 'app/sentryTypes';
44

5+
declare const TestStubs;
6+
57
const withOrganizationMock = WrappedComponent =>
68
class WithOrganizationMockWrapper extends React.Component {
79
static contextTypes = {
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Attempts to get a display name from a Component
22
//
33
// Use for HoCs
4-
export default function getDisplayName(WrappedComponent: React.ComponentType): string {
4+
export default function getDisplayName<Props = {}>(
5+
WrappedComponent: React.ComponentType<Props>
6+
): string {
57
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
68
}

src/sentry/static/sentry/app/utils/withApi.jsx renamed to src/sentry/static/sentry/app/utils/withApi.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@ import getDisplayName from 'app/utils/getDisplayName';
66
/**
77
* HoC that provides "api" client when mounted, and clears API requests when component is unmounted
88
*/
9-
const withApi = WrappedComponent => {
10-
class WithApi extends React.Component {
9+
const withApi = <P extends object>(WrappedComponent: React.ComponentType<P>) => {
10+
return class extends React.Component<Omit<P, 'api'>> {
11+
static displayName = `withApi(${getDisplayName(WrappedComponent)})`;
12+
1113
constructor(props) {
1214
super(props);
1315
this.api = new Client();
1416
}
1517
componentWillUnmount() {
1618
this.api.clear();
1719
}
18-
render() {
19-
return <WrappedComponent api={this.api} {...this.props} />;
20-
}
21-
}
2220

23-
WithApi.displayName = `withApi(${getDisplayName(WrappedComponent)})`;
21+
// TODO(ts): Update this when API client is typed
22+
private api: any;
2423

25-
return WithApi;
24+
render() {
25+
return <WrappedComponent api={this.api as any} {...this.props as P} />;
26+
}
27+
};
2628
};
2729

2830
export default withApi;

src/sentry/static/sentry/app/utils/withConfig.jsx renamed to src/sentry/static/sentry/app/utils/withConfig.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import ConfigStore from 'app/stores/configStore';
77
/**
88
* Higher order component that passes the config object to the wrapped component
99
*/
10-
const withConfig = WrappedComponent =>
10+
const withConfig = <P extends object>(WrappedComponent: React.ComponentType<P>) =>
1111
createReactClass({
1212
displayName: `withConfig(${getDisplayName(WrappedComponent)})`,
1313
mixins: [Reflux.listenTo(ConfigStore, 'onUpdate')],
@@ -24,7 +24,9 @@ const withConfig = WrappedComponent =>
2424
},
2525

2626
render() {
27-
return <WrappedComponent config={this.state.config} {...this.props} />;
27+
return (
28+
<WrappedComponent config={this.state.config as object} {...this.props as P} />
29+
);
2830
},
2931
});
3032

src/sentry/static/sentry/app/utils/withGlobalSelection.jsx renamed to src/sentry/static/sentry/app/utils/withGlobalSelection.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import createReactClass from 'create-react-class';
44

55
import GlobalSelectionStore from 'app/stores/globalSelectionStore';
66
import getDisplayName from 'app/utils/getDisplayName';
7+
import {GlobalSelection} from 'app/types';
78

89
/**
910
* Higher order component that uses GlobalSelectionStore and provides the
1011
* active project
1112
*/
12-
const withGlobalSelection = WrappedComponent =>
13+
const withGlobalSelection = <P extends object>(
14+
WrappedComponent: React.ComponentType<P>
15+
) =>
1316
createReactClass({
1417
displayName: `withGlobalSelection(${getDisplayName(WrappedComponent)})`,
1518
mixins: [Reflux.listenTo(GlobalSelectionStore, 'onUpdate')],
@@ -41,9 +44,9 @@ const withGlobalSelection = WrappedComponent =>
4144
const {forceUrlSync, ...selection} = this.state.selection;
4245
return (
4346
<WrappedComponent
44-
forceUrlSync={forceUrlSync}
45-
selection={selection}
46-
{...this.props}
47+
forceUrlSync={forceUrlSync as boolean}
48+
selection={selection as GlobalSelection}
49+
{...this.props as P}
4750
/>
4851
);
4952
},

src/sentry/static/sentry/app/utils/withLatestContext.jsx renamed to src/sentry/static/sentry/app/utils/withLatestContext.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ import LatestContextStore from 'app/stores/latestContextStore';
88
import SentryTypes from 'app/sentryTypes';
99
import getDisplayName from 'app/utils/getDisplayName';
1010
import withOrganizations from 'app/utils/withOrganizations';
11+
import {Project, Organization} from 'app/types';
1112

12-
// HoC that returns most usable organization + project
13-
// This means your org if you only have 1 org, or
14-
// last accessed organization/project
15-
const withLatestContext = WrappedComponent =>
13+
const withLatestContext = <P extends object>(WrappedComponent: React.ComponentType<P>) =>
1614
withOrganizations(
1715
createReactClass({
1816
displayName: `withLatestContext(${getDisplayName(WrappedComponent)})`,
@@ -24,7 +22,12 @@ const withLatestContext = WrappedComponent =>
2422
render() {
2523
const {organizations} = this.props;
2624
const {latestContext} = this.state;
27-
const {organization, project, lastRoute} = latestContext || {};
25+
const {
26+
organization,
27+
project,
28+
lastRoute,
29+
}: {organization?: Organization; project?: Project; lastRoute?: string} =
30+
latestContext || {};
2831

2932
// Even though org details exists in LatestContextStore,
3033
// fetch organization from OrganizationsStore so that we can
@@ -42,11 +45,11 @@ const withLatestContext = WrappedComponent =>
4245
// project from `latestContext`
4346
return (
4447
<WrappedComponent
45-
organizations={organizations}
46-
organization={latestOrganization}
47-
project={project}
48-
lastRoute={lastRoute}
49-
{...this.props}
48+
organizations={organizations as Organization[]}
49+
organization={latestOrganization as Organization}
50+
project={project as Project}
51+
lastRoute={lastRoute as string}
52+
{...this.props as P}
5053
/>
5154
);
5255
},

src/sentry/static/sentry/app/utils/withOrganization.jsx renamed to src/sentry/static/sentry/app/utils/withOrganization.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ import React from 'react';
22

33
import SentryTypes from 'app/sentryTypes';
44
import getDisplayName from 'app/utils/getDisplayName';
5+
import {Organization} from 'app/types';
56

6-
/**
7-
* Currently wraps component with organization from context
8-
*/
9-
const withOrganization = WrappedComponent =>
10-
class extends React.Component {
7+
const withOrganization = <P extends object>(WrappedComponent: React.ComponentType<P>) =>
8+
class extends React.Component<Omit<P, 'organization'>> {
119
static displayName = `withOrganization(${getDisplayName(WrappedComponent)})`;
1210
static contextTypes = {
1311
organization: SentryTypes.Organization,
1412
};
1513

1614
render() {
1715
return (
18-
<WrappedComponent organization={this.context.organization} {...this.props} />
16+
<WrappedComponent
17+
organization={this.context.organization as Organization}
18+
{...this.props as P}
19+
/>
1920
);
2021
}
2122
};

src/sentry/static/sentry/app/utils/withOrganizations.jsx renamed to src/sentry/static/sentry/app/utils/withOrganizations.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import createReactClass from 'create-react-class';
44

55
import getDisplayName from 'app/utils/getDisplayName';
66
import OrganizationsStore from 'app/stores/organizationsStore';
7+
import {Organization} from 'app/types';
78

8-
const withOrganizations = WrappedComponent =>
9+
const withOrganizations = <P extends object>(WrappedComponent: React.ComponentType<P>) =>
910
createReactClass({
1011
displayName: `withOrganizations(${getDisplayName(WrappedComponent)})`,
1112
mixins: [Reflux.connect(OrganizationsStore, 'organizations')],
1213

1314
render() {
1415
return (
1516
<WrappedComponent
16-
organizationsLoading={!OrganizationsStore.loaded}
17-
organizations={this.state.organizations}
18-
{...this.props}
17+
organizationsLoading={!OrganizationsStore.loaded as boolean}
18+
organizations={this.state.organizations as Organization[]}
19+
{...this.props as P}
1920
/>
2021
);
2122
},

0 commit comments

Comments
 (0)