Browse Source

ref(ui) Remove createReactClass from ProjectContext (#27500)

Mark Story 3 years ago
parent
commit
718630f034
1 changed files with 51 additions and 44 deletions
  1. 51 44
      static/app/views/projects/projectContext.tsx

+ 51 - 44
static/app/views/projects/projectContext.tsx

@@ -1,7 +1,6 @@
+import {Component, createRef} from 'react';
 import DocumentTitle from 'react-document-title';
 import styled from '@emotion/styled';
-import createReactClass from 'create-react-class';
-import Reflux from 'reflux';
 
 import {fetchOrgMembers} from 'app/actionCreators/members';
 import {setActiveProject} from 'app/actionCreators/projects';
@@ -14,7 +13,7 @@ import SentryTypes from 'app/sentryTypes';
 import MemberListStore from 'app/stores/memberListStore';
 import ProjectsStore from 'app/stores/projectsStore';
 import space from 'app/styles/space';
-import {Member, Organization, Project} from 'app/types';
+import {Organization, Project, User} from 'app/types';
 import withApi from 'app/utils/withApi';
 import withOrganization from 'app/utils/withOrganization';
 import withProjects from 'app/utils/withProjects';
@@ -43,7 +42,7 @@ type Props = {
 };
 
 type State = {
-  memberList: Member[];
+  memberList: User[];
   project: Project | null;
   loading: boolean;
   error: boolean;
@@ -57,19 +56,14 @@ type State = {
  * Additionally delays rendering of children until project XHR has finished
  * and context is populated.
  */
-const ProjectContext = createReactClass<Props, State>({
-  displayName: 'ProjectContext',
-
-  childContextTypes: {
+class ProjectContext extends Component<Props, State> {
+  static childContextTypes = {
     project: SentryTypes.Project,
-  },
+  };
 
-  mixins: [
-    Reflux.connect(MemberListStore, 'memberList') as any,
-    Reflux.listenTo(ProjectsStore, 'onProjectChange') as any,
-  ],
+  state = this.getInitialState();
 
-  getInitialState() {
+  getInitialState(): State {
     return {
       loading: true,
       error: false,
@@ -77,17 +71,17 @@ const ProjectContext = createReactClass<Props, State>({
       memberList: [],
       project: null,
     };
-  },
+  }
 
   getChildContext() {
     return {
       project: this.state.project,
     };
-  },
+  }
 
   componentWillMount() {
     this.fetchData();
-  },
+  }
 
   componentWillReceiveProps(nextProps: Props) {
     if (nextProps.projectId === this.props.projectId) {
@@ -97,7 +91,7 @@ const ProjectContext = createReactClass<Props, State>({
     if (!nextProps.skipReload) {
       this.remountComponent();
     }
-  },
+  }
 
   componentDidUpdate(prevProps: Props, prevState: State) {
     if (prevProps.projectId !== this.props.projectId) {
@@ -125,23 +119,38 @@ const ProjectContext = createReactClass<Props, State>({
 
     // intentionally shallow comparing references
     if (prevState.project !== this.state.project) {
-      if (!this.docTitle) {
+      const docTitle = this.docTitleRef.current;
+      if (!docTitle) {
         return;
       }
-      const docTitle = this.docTitleRef.docTitle;
-      if (docTitle) {
-        docTitle.forceUpdate();
-      }
+      docTitle.forceUpdate();
     }
-  },
+  }
+
+  componentWillUnmount() {
+    this.unsubscribeMembers();
+    this.unsubscribeProjects();
+  }
+
+  docTitleRef = createRef<DocumentTitle>();
+
+  unsubscribeProjects = ProjectsStore.listen(
+    (projectIds: Set<string>) => this.onProjectChange(projectIds),
+    undefined
+  );
+
+  unsubscribeMembers = MemberListStore.listen(
+    (memberList: MemberListStore['state']) => this.setState({memberList}),
+    undefined
+  );
 
   remountComponent() {
-    this.setState(this.getInitialState!());
-  },
+    this.setState(this.getInitialState());
+  }
 
   getTitle() {
     return this.state.project?.slug ?? 'Sentry';
-  },
+  }
 
   onProjectChange(projectIds: Set<string>) {
     if (!this.state.project) {
@@ -150,17 +159,16 @@ const ProjectContext = createReactClass<Props, State>({
     if (!projectIds.has(this.state.project.id)) {
       return;
     }
-
     this.setState({
-      project: {...ProjectsStore.getById(this.state.project.id)},
+      project: {...ProjectsStore.getById(this.state.project.id)} as Project,
     });
-  },
+  }
 
   identifyProject() {
     const {projects, projectId} = this.props;
     const projectSlug = projectId;
     return projects.find(({slug}) => slug === projectSlug) || null;
-  },
+  }
 
   async fetchData() {
     const {orgId, projectId, skipReload} = this.props;
@@ -200,7 +208,7 @@ const ProjectContext = createReactClass<Props, State>({
         });
       }
 
-      fetchOrgMembers(this.props.api, orgId, activeProject.id);
+      fetchOrgMembers(this.props.api, orgId, [activeProject.id]);
 
       return;
     }
@@ -228,10 +236,11 @@ const ProjectContext = createReactClass<Props, State>({
         errorType: ErrorTypes.PROJECT_NOT_FOUND,
       });
     }
-  },
+  }
 
   renderBody() {
-    if (this.state.loading) {
+    const {error, errorType, loading, project} = this.state;
+    if (loading) {
       return (
         <div className="loading-full-layout">
           <LoadingIndicator />
@@ -239,15 +248,13 @@ const ProjectContext = createReactClass<Props, State>({
       );
     }
 
-    if (!this.state.error) {
+    if (!error && project) {
       const {children} = this.props;
 
-      return typeof children === 'function'
-        ? children({project: this.state.project})
-        : children;
+      return typeof children === 'function' ? children({project}) : children;
     }
 
-    switch (this.state.errorType) {
+    switch (errorType) {
       case ErrorTypes.PROJECT_NOT_FOUND:
         // TODO(chrissy): use scale for margin values
         return (
@@ -264,23 +271,23 @@ const ProjectContext = createReactClass<Props, State>({
           <ErrorWrapper>
             <MissingProjectMembership
               organization={this.props.organization}
-              projectSlug={this.state.project.slug}
+              projectSlug={project?.slug}
             />
           </ErrorWrapper>
         );
       default:
         return <LoadingError onRetry={this.remountComponent} />;
     }
-  },
+  }
 
   render() {
     return (
-      <DocumentTitle ref={ref => (this.docTitleRef = ref)} title={this.getTitle()}>
+      <DocumentTitle ref={this.docTitleRef} title={this.getTitle()}>
         {this.renderBody()}
       </DocumentTitle>
     );
-  },
-});
+  }
+}
 
 export {ProjectContext};