diff --git a/commands.js b/commands.js
index 72bd036..136cf26 100644
--- a/commands.js
+++ b/commands.js
@@ -88,4 +88,10 @@ export default {
 		}
 		app.client.send({ command: "TOPIC", params });
 	},
+	"reconnect": (app, args) => {
+		app.reconnect();
+	},
+	"disconnect": (app, args) => {
+		app.disconnect();
+	},
 };
diff --git a/components/app.js b/components/app.js
index f57bdfd..4d92e8b 100644
--- a/components/app.js
+++ b/components/app.js
@@ -107,10 +107,12 @@ export default class App extends Component {
 	receipts = new Map();
 	buffer = createRef();
 	composer = createRef();
+	reconnectTimeoutID = null;
 
 	constructor(props) {
 		super(props);
 
+		this.handleClose = this.handleClose.bind(this);
 		this.handleConnectSubmit = this.handleConnectSubmit.bind(this);
 		this.handleBufferListClick = this.handleBufferListClick.bind(this);
 		this.handleComposerSubmit = this.handleComposerSubmit.bind(this);
@@ -347,6 +349,8 @@ export default class App extends Component {
 	}
 
 	connect(params) {
+		this.disconnect();
+
 		this.setState({ status: Status.CONNECTING, connectParams: params });
 
 		this.client = new Client({
@@ -358,19 +362,7 @@ export default class App extends Component {
 			saslPlain: params.saslPlain,
 		});
 
-		this.client.addEventListener("close", () => {
-			this.setState((state) => {
-				if (state.status == Status.DISCONNECTED) {
-					// User decided to logout
-					return null;
-				}
-				console.log("Reconnecting to server in " + RECONNECT_DELAY_SEC + " seconds");
-				setTimeout(() => {
-					this.connect(params);
-				}, RECONNECT_DELAY_SEC * 1000);
-				return { status: Status.DISCONNECTED };
-			});
-		});
+		this.client.addEventListener("close", this.handleClose);
 
 		this.client.addEventListener("message", (event) => {
 			this.handleMessage(event.detail.message);
@@ -386,6 +378,38 @@ export default class App extends Component {
 		this.switchBuffer(SERVER_BUFFER);
 	}
 
+	handleClose() {
+		this.setState((state) => {
+			if (state.status == Status.DISCONNECTED) {
+				// User decided to logout
+				return null;
+			}
+			console.log("Reconnecting to server in " + RECONNECT_DELAY_SEC + " seconds");
+			clearTimeout(this.reconnectTimeoutID);
+			this.reconnectTimeoutID = setTimeout(() => {
+				this.connect(this.state.connectParams);
+			}, RECONNECT_DELAY_SEC * 1000);
+			return { status: Status.DISCONNECTED };
+		});
+	}
+
+	disconnect() {
+		clearTimeout(this.reconnectTimeoutID);
+		this.reconnectTimeoutID = null;
+
+		if (this.client) {
+			// Prevent auto-reconnect from kicking in
+			this.client.removeEventListener("close", this.handleClose);
+			this.client.close();
+		}
+
+		this.setState({ status: Status.DISCONNECTED });
+	}
+
+	reconnect() {
+		this.connect(this.state.connectParams);
+	}
+
 	handleMessage(msg) {
 		switch (msg.command) {
 		case irc.RPL_WELCOME:
@@ -614,11 +638,10 @@ export default class App extends Component {
 	close(target) {
 		if (target == SERVER_BUFFER) {
 			this.setState({
-				status: Status.DISCONNECTED,
 				buffers: new Map(),
 				activeBuffer: null,
 			});
-			this.client.close();
+			this.disconnect();
 			return;
 		}