Skip to content

Commit

Permalink
Convert to React Hooks (#147)
Browse files Browse the repository at this point in the history
This also involves migrating from mobx-react to mobx-react-lite.
  • Loading branch information
fwouts authored Apr 19, 2019
1 parent 22c9256 commit 123d4df
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 325 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"assert-never": "^1.1.0",
"bootstrap": "^4.3.1",
"mobx": "^5.9.4",
"mobx-react": "^5.4.3",
"mobx-react-lite": "^1.2.0",
"react": "^16.8.6",
"react-bootstrap": "^1.0.0-beta.8",
"react-dom": "^16.8.6",
Expand Down
17 changes: 7 additions & 10 deletions src/components/Error.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from "@emotion/styled";
import { observer } from "mobx-react";
import React, { Component } from "react";
import { observer } from "mobx-react-lite";
import React from "react";

export interface ErrorProps {
lastError: string | null;
Expand All @@ -13,12 +13,9 @@ const ErrorContainer = styled.p`
padding: 8px;
`;

@observer
export class Error extends Component<ErrorProps> {
render() {
if (!this.props.lastError) {
return <></>;
}
return <ErrorContainer>Error: {this.props.lastError}</ErrorContainer>;
export const Error = observer((props: ErrorProps) => {
if (!props.lastError) {
return <></>;
}
}
return <ErrorContainer>Error: {props.lastError}</ErrorContainer>;
});
19 changes: 7 additions & 12 deletions src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import styled from "@emotion/styled";
import { observer } from "mobx-react";
import React, { Component } from "react";
import { observer } from "mobx-react-lite";
import React from "react";
import { ClipLoader } from "react-spinners";
import { Center } from "./design/Center";

const PaddedCenter = styled(Center)`
padding: 16px;
`;

@observer
export class Loader extends Component<{}> {
render() {
return (
<PaddedCenter>
<ClipLoader />
</PaddedCenter>
);
}
}
export const Loader = observer(() => (
<PaddedCenter>
<ClipLoader />
</PaddedCenter>
));
228 changes: 111 additions & 117 deletions src/components/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import React, { Component } from "react";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
import { Badge, Tab, Tabs } from "react-bootstrap";
import { Filter } from "../filtering/filters";
import { Core } from "../state/core";
Expand All @@ -16,131 +16,125 @@ export interface PopupState {
currentFilter: Filter;
}

@observer
export class Popup extends Component<PopupProps, PopupState> {
state = {
export const Popup = observer((props: PopupProps) => {
const [state, setState] = useState<PopupState>({
currentFilter: Filter.INCOMING
};

async componentDidMount() {
await this.props.core.load();
await this.props.core.refreshPullRequests();
}
});

render() {
return (
<>
<Error lastError={this.props.core.lastError} />
{this.props.core.token && !this.props.core.lastError && (
<>
<Tabs
id="popup-tabs"
activeKey={this.state.currentFilter}
onSelect={(key: Filter) => this.setState({ currentFilter: key })}
>
<Tab
title={
<>
Incoming PRs{" "}
{this.props.core.filteredPullRequests && (
<Badge
variant={
this.props.core.filteredPullRequests.incoming.length >
0
? "danger"
: "secondary"
}
>
{this.props.core.filteredPullRequests.incoming.length}
</Badge>
)}
</>
}
eventKey={Filter.INCOMING}
/>
<Tab
title={
<>
Muted{" "}
{this.props.core.filteredPullRequests && (
<Badge variant="secondary">
{this.props.core.filteredPullRequests.muted.length}
</Badge>
)}
</>
}
eventKey={Filter.MUTED}
/>
<Tab
title={
<>
Already reviewed{" "}
{this.props.core.filteredPullRequests && (
<Badge variant="secondary">
{this.props.core.filteredPullRequests.reviewed.length}
</Badge>
)}
</>
}
eventKey={Filter.REVIEWED}
/>
<Tab
title={
<>
My PRs{" "}
{this.props.core.filteredPullRequests && (
<Badge variant="secondary">
{this.props.core.filteredPullRequests.mine.length}
</Badge>
)}
</>
}
eventKey={Filter.MINE}
/>
</Tabs>
<PullRequestList
pullRequests={
this.props.core.filteredPullRequests
? this.props.core.filteredPullRequests[
this.state.currentFilter
]
: null
}
emptyMessage={
this.state.currentFilter === Filter.INCOMING
? `Nothing to review, yay!`
: `There's nothing to see here.`
}
allowMuting={
this.state.currentFilter === Filter.INCOMING ||
this.state.currentFilter === Filter.MUTED
}
onOpen={this.onOpen}
onMute={this.onMute}
/>
</>
)}
{this.props.core.overallStatus !== "loading" && (
<Settings core={this.props.core} />
)}
</>
);
}
useEffect(() => {
props.core
.load()
.then(() => props.core.refreshPullRequests())
.catch(console.error);
}, []);

private onOpen = (pullRequestUrl: string) => {
this.props.core.openPullRequest(pullRequestUrl).catch(console.error);
const onOpen = (pullRequestUrl: string) => {
props.core.openPullRequest(pullRequestUrl).catch(console.error);
};

private onMute = (pullRequest: PullRequest) => {
switch (this.state.currentFilter) {
const onMute = (pullRequest: PullRequest) => {
switch (state.currentFilter) {
case Filter.INCOMING:
this.props.core.mutePullRequest(pullRequest);
props.core.mutePullRequest(pullRequest);
break;
case Filter.MUTED:
this.props.core.unmutePullRequest(pullRequest);
props.core.unmutePullRequest(pullRequest);
break;
default:
// Do nothing.
}
};
}

return (
<>
<Error lastError={props.core.lastError} />
{props.core.token && !props.core.lastError && (
<>
<Tabs
id="popup-tabs"
activeKey={state.currentFilter}
onSelect={(key: Filter) => setState({ currentFilter: key })}
>
<Tab
title={
<>
Incoming PRs{" "}
{props.core.filteredPullRequests && (
<Badge
variant={
props.core.filteredPullRequests.incoming.length > 0
? "danger"
: "secondary"
}
>
{props.core.filteredPullRequests.incoming.length}
</Badge>
)}
</>
}
eventKey={Filter.INCOMING}
/>
<Tab
title={
<>
Muted{" "}
{props.core.filteredPullRequests && (
<Badge variant="secondary">
{props.core.filteredPullRequests.muted.length}
</Badge>
)}
</>
}
eventKey={Filter.MUTED}
/>
<Tab
title={
<>
Already reviewed{" "}
{props.core.filteredPullRequests && (
<Badge variant="secondary">
{props.core.filteredPullRequests.reviewed.length}
</Badge>
)}
</>
}
eventKey={Filter.REVIEWED}
/>
<Tab
title={
<>
My PRs{" "}
{props.core.filteredPullRequests && (
<Badge variant="secondary">
{props.core.filteredPullRequests.mine.length}
</Badge>
)}
</>
}
eventKey={Filter.MINE}
/>
</Tabs>
<PullRequestList
pullRequests={
props.core.filteredPullRequests
? props.core.filteredPullRequests[state.currentFilter]
: null
}
emptyMessage={
state.currentFilter === Filter.INCOMING
? `Nothing to review, yay!`
: `There's nothing to see here.`
}
allowMuting={
state.currentFilter === Filter.INCOMING ||
state.currentFilter === Filter.MUTED
}
onOpen={onOpen}
onMute={onMute}
/>
</>
)}
{props.core.overallStatus !== "loading" && <Settings core={props.core} />}
</>
);
});
70 changes: 33 additions & 37 deletions src/components/PullRequestItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from "@emotion/styled";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { observer } from "mobx-react";
import React, { Component } from "react";
import { observer } from "mobx-react-lite";
import React from "react";
import { PullRequest } from "../storage/loaded-state";
import { SmallButton } from "./design/Button";

Expand Down Expand Up @@ -77,44 +77,40 @@ export interface PullRequestItemProps {
onMute(pullRequest: PullRequest): void;
}

@observer
export class PullRequestItem extends Component<PullRequestItemProps> {
render() {
return (
<PullRequestBox key={this.props.pullRequest.nodeId} onClick={this.open}>
<Info>
<Title>
{this.props.pullRequest.title}
{this.props.allowMuting && (
<SmallButton title="Mute until next update" onClick={this.mute}>
<FontAwesomeIcon icon="bell-slash" />
</SmallButton>
)}
</Title>
<Repo>
{this.props.pullRequest.repoOwner}/{this.props.pullRequest.repoName}{" "}
(#
{this.props.pullRequest.pullRequestNumber})
</Repo>
</Info>
<AuthorBox title={this.props.pullRequest.authorLogin}>
{this.props.pullRequest.author && (
<AuthorAvatar src={this.props.pullRequest.author.avatarUrl} />
)}
<AuthorLogin>{this.props.pullRequest.authorLogin}</AuthorLogin>
</AuthorBox>
</PullRequestBox>
);
}

private open = (e: React.MouseEvent) => {
this.props.onOpen(this.props.pullRequest.htmlUrl);
export const PullRequestItem = observer((props: PullRequestItemProps) => {
const open = (e: React.MouseEvent) => {
props.onOpen(props.pullRequest.htmlUrl);
e.preventDefault();
};

private mute = (e: React.MouseEvent) => {
this.props.onMute(this.props.pullRequest);
const mute = (e: React.MouseEvent) => {
props.onMute(props.pullRequest);
e.preventDefault();
e.stopPropagation();
};
}

return (
<PullRequestBox key={props.pullRequest.nodeId} onClick={open}>
<Info>
<Title>
{props.pullRequest.title}
{props.allowMuting && (
<SmallButton title="Mute until next update" onClick={mute}>
<FontAwesomeIcon icon="bell-slash" />
</SmallButton>
)}
</Title>
<Repo>
{props.pullRequest.repoOwner}/{props.pullRequest.repoName} (#
{props.pullRequest.pullRequestNumber})
</Repo>
</Info>
<AuthorBox title={props.pullRequest.authorLogin}>
{props.pullRequest.author && (
<AuthorAvatar src={props.pullRequest.author.avatarUrl} />
)}
<AuthorLogin>{props.pullRequest.authorLogin}</AuthorLogin>
</AuthorBox>
</PullRequestBox>
);
});
Loading

0 comments on commit 123d4df

Please sign in to comment.