Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Commit

Permalink
Minor refactoring (fixes #21)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdemin committed Jan 5, 2020
1 parent ba22fa0 commit d3e1c7c
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 28 deletions.
25 changes: 25 additions & 0 deletions src/typings/react.ts
@@ -1,4 +1,9 @@
import React from "react";
import { Dispatch as DispatchFunction } from "redux";
import { ThunkDispatch } from "redux-thunk";
import { RouteComponentProps } from "react-router";

import { AnyAction } from "./actions";

/** Generic type for React children. */
export type ReactDescendant =
Expand All @@ -8,3 +13,23 @@ export type ReactDescendant =
| string
| React.ReactNode
| React.ReactNode[];

/** Dispatching function, possibly using Redux-Thunk middleware. */
export type Dispatch = DispatchFunction & ThunkDispatch<any, any, AnyAction>;

/** Component properties passed by by React-Redux. */
export type ReduxProps = {
dispatch: Dispatch;
};

/** Route component props, including dispatching function. */
export type RCPWithDispatch = RouteComponentProps & {
dispatch: Dispatch;
};

type AnyFunction = (...args: any) => any;

/** Route component props, extended by mapDispatchToProps return value. Pass
* `typeof func` as the generic parameter. */
export type RCPWithDispProps<func extends AnyFunction> = RouteComponentProps &
ReturnType<func>;
24 changes: 14 additions & 10 deletions src/views/editorView.tsx
@@ -1,6 +1,5 @@
import React from "react";
import { ThunkDispatch } from "redux-thunk";
import { match as Match, RouteComponentProps } from "react-router";
import { match as Match } from "react-router";
import { connect } from "react-redux";

import Container from "./components/bulma/container";
Expand All @@ -12,9 +11,9 @@ import TaskSelect from "./components/taskSelect";

import { deleteTask, updateTask, createTask } from "../actions/tasks";
import { hotkeyHandler, escCode, Hotkey } from "./helpers/keyboard";
import { TaskAction } from "../typings/actions";
import { Task } from "../typings/tasks";
import { Store } from "../typings/store";
import { Dispatch, RCPWithDispProps } from "../typings/react";

import strings from "./assets/locales";

Expand Down Expand Up @@ -64,12 +63,17 @@ const mapStateToProps = (state: Store) => ({
tasks: state.task.tasks,
});

const mapDispatchToProps = (dispatch: Dispatch) => ({
create: (task: Task) => dispatch(createTask(task)),
update: (task: Task) => dispatch(updateTask(task)),
delete: (task: Task) => dispatch(deleteTask(task)),
});

/** Used to delay navigation before we are finished with stuff. */
const waitTimeout = 200;

interface Props extends RouteComponentProps {
interface Props extends RCPWithDispProps<typeof mapDispatchToProps> {
tasks: Task[];
dispatch: ThunkDispatch<any, any, TaskAction>;
match: Match<{ id: string }>;
}
interface State {
Expand All @@ -90,6 +94,7 @@ class EditorView extends React.Component<Props, State> {
};
hotkeyHandler = generateHotkeyHandler(this);
componentDidMount = () => {
document.addEventListener("keydown", this.hotkeyHandler);
// editing or creating a new task?
if (this.props.match.params.id !== "new") {
const id = parseInt(this.props.match.params.id);
Expand All @@ -116,13 +121,12 @@ class EditorView extends React.Component<Props, State> {
title: strings.editor_newTaskTitle,
});
}
document.addEventListener("keydown", this.hotkeyHandler);
};
componentWillUnmount = () => {
document.removeEventListener("keydown", this.hotkeyHandler);
};
delete = () => {
this.props.dispatch(deleteTask(this.state.task));
this.props.delete(this.state.task);
setTimeout(() => {
this.props.history.goBack();
}, waitTimeout);
Expand All @@ -145,9 +149,9 @@ class EditorView extends React.Component<Props, State> {
};
saveChanges = () => {
if (this.state.newTask) {
this.props.dispatch(createTask(this.state.task));
this.props.create(this.state.task);
} else {
this.props.dispatch(updateTask(this.state.task));
this.props.update(this.state.task);
}
// delay the reload so fetching tasks doesn't proceed before we have
// pushed stuff to the server
Expand Down Expand Up @@ -239,4 +243,4 @@ class EditorView extends React.Component<Props, State> {
};
}

export default connect(mapStateToProps)(EditorView);
export default connect(mapStateToProps, mapDispatchToProps)(EditorView);
16 changes: 8 additions & 8 deletions src/views/loginForm.tsx
@@ -1,7 +1,5 @@
import React from "react";
import { ThunkDispatch } from "redux-thunk";
import { connect } from "react-redux";
import { RouteComponentProps } from "react-router-dom";

import Container from "./components/bulma/container";
import Button from "./components/bulma/button";
Expand All @@ -13,7 +11,7 @@ import Message from "./components/message";

import { login } from "../actions/auth";
import { getServerVersion } from "../actions/misc";
import { AuthAction } from "../typings/actions";
import { Dispatch, RCPWithDispProps } from "../typings/react";
import { Store } from "../typings/store";

import strings from "./assets/locales";
Expand All @@ -22,16 +20,19 @@ const mapStateToProps = (state: Store) => ({
loginFailed: state.auth.loginFailed,
});

const mapDispatchToProps = (dispatch: Dispatch) => ({
login: (user: string, pass: string) => dispatch(login(user, pass)),
});

// applies to the state as well
interface State {
username: string;
password: string;
loginFailed: boolean;
signupEnabled: boolean;
}
interface Props extends RouteComponentProps {
interface Props extends RCPWithDispProps<typeof mapDispatchToProps> {
loginFailed?: boolean;
dispatch: ThunkDispatch<any, any, AuthAction>;
}
class LoginForm extends React.PureComponent<Props, State> {
state = {
Expand All @@ -53,8 +54,7 @@ class LoginForm extends React.PureComponent<Props, State> {
this.setState({ username: event.currentTarget.value });
updatePassword = (event: React.FormEvent<HTMLInputElement>) =>
this.setState({ password: event.currentTarget.value });
login = () =>
this.props.dispatch(login(this.state.username, this.state.password));
login = () => this.props.login(this.state.username, this.state.password);
toSignup = () => this.props.history.push("/signup");
onKeyPress = (event: React.KeyboardEvent<HTMLElement>) => {
event.key === "Enter" && this.login();
Expand Down Expand Up @@ -96,4 +96,4 @@ class LoginForm extends React.PureComponent<Props, State> {
);
}

export default connect(mapStateToProps)(LoginForm);
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
23 changes: 13 additions & 10 deletions src/views/mainView.tsx
@@ -1,7 +1,5 @@
import React from "react";
import { ThunkDispatch } from "redux-thunk";
import { connect } from "react-redux";
import { RouteComponentProps } from "react-router-dom";

import Container from "./components/bulma/container";
import Button from "./components/bulma/button";
Expand All @@ -13,9 +11,9 @@ import Footer from "./components/footer";

import { logout } from "../actions/auth";
import { refetchTasks, deleteTask } from "../actions/tasks";
import { AnyAction } from "../typings/actions";
import { Task } from "../typings/tasks";
import { Store } from "../typings/store";
import { Dispatch, RCPWithDispProps } from "../typings/react";

import { uiDelay } from "../const";
import { hotkeyHandler, escCode } from "./helpers/keyboard";
Expand Down Expand Up @@ -67,8 +65,13 @@ const mapStateToProps = (state: Store) => ({
username: state.auth.username,
});

interface Props extends RouteComponentProps {
dispatch: ThunkDispatch<any, any, AnyAction>;
const mapDispatchToProps = (dispatch: Dispatch) => ({
logout: () => dispatch(logout()),
refetch: (tasks: Task[]) => dispatch(refetchTasks(tasks)),
delete: (task: Task) => dispatch(deleteTask(task)),
});

interface Props extends RCPWithDispProps<typeof mapDispatchToProps> {
tasks: Task[];
username?: string;
}
Expand All @@ -83,8 +86,8 @@ class MainView extends React.Component<Props, State> {
};
hotkeyHandler = generateHotkeyHandler(this);
componentDidMount = () => {
this.refetch();
document.addEventListener("keydown", this.hotkeyHandler);
this.refetch();
};
componentWillUnmount = () => {
document.removeEventListener("keydown", this.hotkeyHandler);
Expand All @@ -97,15 +100,15 @@ class MainView extends React.Component<Props, State> {
}
};
toNewTask = () => this.props.history.push("/task/new");
logout = () => this.props.dispatch(logout());
refetch = () => this.props.dispatch(refetchTasks(this.props.tasks));
logout = () => this.props.logout();
refetch = () => this.props.refetch(this.props.tasks);
prune = () => {
const danglingTasks = this.state.tasks.filter(
(task) =>
this.state.tasks.filter((child) => child.PID === task.ID)
.length === 0 && task.Completed
);
danglingTasks.forEach((task) => this.props.dispatch(deleteTask(task)));
danglingTasks.forEach((task) => this.props.delete(task));
// more tasks possibly left to go?
danglingTasks.length > 0 && setTimeout(() => this.prune(), uiDelay);
};
Expand Down Expand Up @@ -171,4 +174,4 @@ class MainView extends React.Component<Props, State> {
};
}

export default connect(mapStateToProps)(MainView);
export default connect(mapStateToProps, mapDispatchToProps)(MainView);

0 comments on commit d3e1c7c

Please sign in to comment.