diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 68ea04de8c0a14621ec980cf503c6d09e04a9038..0000000000000000000000000000000000000000
--- a/.prettierrc
+++ /dev/null
@@ -1 +0,0 @@
-tabWidth: 4
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae7b27fbfa2fd96a0c3b4dd4fe2a529452e1c4ff
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+    "tabWidth": 4,
+    "useTabs": true,
+    "trailingComma": "all",
+    "jsxBracketSameLine": true,
+    "importOrder": ["/(lib)", "/(redux)", "/(context)", "/(ui|common)|.svg$", "^[./]"],
+    "importOrderSeparation": true,
+}
+  
\ No newline at end of file
diff --git a/package.json b/package.json
index ec6f6d2d21dace77f9b543710b27198c256745fd..2e298ff3fcfa9c664948d9fce65a63aee4f166b5 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
     "@tippyjs/react": "^4.2.5",
     "@traptitech/markdown-it-katex": "^3.4.3",
     "@traptitech/markdown-it-spoiler": "^1.1.6",
+    "@trivago/prettier-plugin-sort-imports": "^2.0.2",
     "@types/lodash.defaultsdeep": "^4.6.6",
     "@types/lodash.isequal": "^4.5.5",
     "@types/markdown-it": "^12.0.2",
diff --git a/src/components/common/LocaleSelector.tsx b/src/components/common/LocaleSelector.tsx
index 225e2c389626e5dfb7ff41a00bd87e86093444e2..7f91b1c7a3063eec5f63dd373c797dee3819af81 100644
--- a/src/components/common/LocaleSelector.tsx
+++ b/src/components/common/LocaleSelector.tsx
@@ -1,9 +1,9 @@
 import ComboBox from "../ui/ComboBox";
+import { dispatch } from "../../redux";
 import { connectState } from "../../redux/connector";
-import { WithDispatcher } from "../../redux/reducers";
 import { Language, LanguageEntry, Languages } from "../../context/Locale";
 
-type Props = WithDispatcher & {
+type Props = {
     locale: string;
 };
 
@@ -12,8 +12,7 @@ export function LocaleSelector(props: Props) {
         <ComboBox
             value={props.locale}
             onChange={e =>
-                props.dispatcher &&
-                props.dispatcher({
+                dispatch({
                     type: "SET_LOCALE",
                     locale: e.currentTarget.value as Language
                 })
@@ -37,6 +36,5 @@ export default connectState(
         return {
             locale: state.locale
         };
-    },
-    true
+    }
 );
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index b035bfa627a2af9d4a317bd4e63e3b3b06641340..486f6f28a8da03f0477f3477cc7b95764aae47ff 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -1,11 +1,11 @@
 import { ulid } from "ulid";
 import { Text } from "preact-i18n";
-import Tooltip, { PermissionTooltip } from "../Tooltip";
 import { Channel } from "revolt.js";
 import styled from "styled-components";
+import { dispatch } from "../../../redux";
 import { defer } from "../../../lib/defer";
 import IconButton from "../../ui/IconButton";
-import { X } from '@styled-icons/boxicons-regular';
+import { PermissionTooltip } from "../Tooltip";
 import { Send } from '@styled-icons/boxicons-solid';
 import { debounce } from "../../../lib/debounce";
 import Axios, { CancelTokenSource } from "axios";
@@ -13,7 +13,6 @@ import { useTranslation } from "../../../lib/i18n";
 import { Reply } from "../../../redux/reducers/queue";
 import { connectState } from "../../../redux/connector";
 import { SoundContext } from "../../../context/Settings";
-import { WithDispatcher } from "../../../redux/reducers";
 import { takeError } from "../../../context/revoltjs/util";
 import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
 import AutoComplete, { useAutoComplete } from "../AutoComplete";
@@ -30,9 +29,8 @@ import { ShieldX } from "@styled-icons/boxicons-regular";
 
 import ReplyBar from "./bars/ReplyBar";
 import FilePreview from './bars/FilePreview';
-import { Styleshare } from "@styled-icons/simple-icons";
 
-type Props = WithDispatcher & {
+type Props = {
     channel: Channel;
     draft?: string;
 };
@@ -77,7 +75,7 @@ const Action = styled.div`
 // ! FIXME: add to app config and load from app config
 export const CAN_UPLOAD_AT_ONCE = 5;
 
-function MessageBox({ channel, draft, dispatcher }: Props) {
+function MessageBox({ channel, draft }: Props) {
     const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
     const [ typing, setTyping ] = useState<boolean | number>(false);
     const [ replies, setReplies ] = useState<Reply[]>([]);
@@ -102,13 +100,13 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
 
     function setMessage(content?: string) {
         if (content) {
-            dispatcher({
+            dispatch({
                 type: "SET_DRAFT",
                 channel: channel._id,
                 content
             });
         } else {
-            dispatcher({
+            dispatch({
                 type: "CLEAR_DRAFT",
                 channel: channel._id
             });
@@ -148,7 +146,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
         playSound('outbound');
 
         const nonce = ulid();
-        dispatcher({
+        dispatch({
             type: "QUEUE_ADD",
             nonce,
             channel: channel._id,
@@ -171,7 +169,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
                 replies
             });
         } catch (error) {
-            dispatcher({
+            dispatch({
                 type: "QUEUE_FAIL",
                 error: takeError(error),
                 nonce
@@ -383,7 +381,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
     )
 }
 
-export default connectState<Omit<Props, "dispatcher" | "draft">>(MessageBox, (state, { channel }) => {
+export default connectState<Omit<Props, "dispatch" | "draft">>(MessageBox, (state, { channel }) => {
     return {
         draft: state.drafts[channel._id]
     }
diff --git a/src/components/common/messaging/attachments/Attachment.module.scss b/src/components/common/messaging/attachments/Attachment.module.scss
index 5aa89744d83a997be557862d27105019ed2cfaf2..8b5529d41e8eab28e646c2a06f5fb6bf0d36e4e0 100644
--- a/src/components/common/messaging/attachments/Attachment.module.scss
+++ b/src/components/common/messaging/attachments/Attachment.module.scss
@@ -1,13 +1,12 @@
 .attachment {
-    border-radius: 6px;
-    margin: .125rem 0 .125rem;
+    display: grid;
+    grid-auto-columns: min(100%, 480px);
+    grid-auto-flow: row dense;
     
-    height: auto;
+    width: max-content;
     
-    max-height: 640px;
-    max-width: min(480px, 100%);
-    
-    object-fit: contain;
+    border-radius: 6px;
+    margin: .125rem 0 .125rem;
     
     &[data-spoiler="true"] {
         filter: blur(30px);
@@ -20,6 +19,16 @@
 
     &.image {
         cursor: pointer;
+        
+        max-height: 640px;
+        max-width: min(480px, 100%);
+        
+        object-fit: contain;
+
+        &.loaded {
+            width: auto;
+            height: auto;
+        }
     }
 
     &.video {        
@@ -29,8 +38,15 @@
         }
 
         video {
-            width: 100%;
             border-radius: 0 0 6px 6px;
+            
+            max-height: 640px;
+            max-width: min(480px, 100%);
+        }
+
+        video.loaded {
+            width: auto;
+            height: auto;
         }
     }
 
@@ -59,11 +75,12 @@
     }
 
     &.text {
-        display: flex;
-        overflow: hidden;
+        width: 100%;
         max-width: 800px;
+        overflow: hidden;
+        grid-auto-columns: unset;
+
         border-radius: 6px;
-        flex-direction: column;
 
         .textContent {
             height: 140px;
@@ -92,35 +109,48 @@
     }
 }
 
+.actions.imageAction {
+    grid-template: 
+        "name icon download" auto
+        "size icon download" auto
+        / minmax(20px, 1fr) min-content min-content;
+}
+
 .actions {
-    gap: 8px;
+    display: grid;
+    grid-template: 
+        "icon name download" auto
+        "icon size download" auto
+        / min-content minmax(20px, 1fr) min-content;
+
+    align-items: center;
+    column-gap: 8px;
+    
+    width: 100%;
     padding: 8px;
-    display: flex;
     overflow: none;
-    max-width: 100%;
-    align-items: center;
-    flex-direction: row;
+
     color: var(--foreground);
     background: var(--secondary-background);
 
-    > svg {
-        flex-shrink: 0;
+    span {
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow: hidden;
     }
     
-    .info {
-        display: flex;
-        flex-direction: column;
-        flex-grow: 1;
+    .filesize {
+        grid-area: size;
 
-        > span {
-            text-overflow: ellipsis;
-            white-space: nowrap;
-            overflow: hidden;
-        }
+        font-size: 10px;
+        color: var(--secondary-foreground);
+    }
 
-        .filesize {
-            font-size: 10px;
-            color: var(--secondary-foreground);
-        }
+    .downloadIcon {
+        grid-area: download;
+    }
+
+    .iconType {
+        grid-area: icon;
     }
 }
diff --git a/src/components/common/messaging/attachments/Attachment.tsx b/src/components/common/messaging/attachments/Attachment.tsx
index 15c4253658859c3a910e4c3407385be13e8a7036..2ef979a2c8c22b29d0fbbd835cb3aad8357f44df 100644
--- a/src/components/common/messaging/attachments/Attachment.tsx
+++ b/src/components/common/messaging/attachments/Attachment.tsx
@@ -21,6 +21,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
     const { openScreen } = useIntermediate();
     const { filename, metadata } = attachment;
     const [ spoiler, setSpoiler ] = useState(filename.startsWith("SPOILER_"));
+    const [ loaded, setLoaded ] = useState(false)
 
     const url = client.generateFileURL(attachment, { width: MAX_ATTACHMENT_WIDTH * 1.5 }, true);
 
@@ -44,7 +45,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
                         height={metadata.height}
                         data-spoiler={spoiler}
                         data-has-content={hasContent}
-                        className={classNames(styles.attachment, styles.image)}
+                        className={classNames(styles.attachment, styles.image, loaded && styles.loaded)}
                         onClick={() =>
                             openScreen({ id: "image_viewer", attachment })
                         }
@@ -52,6 +53,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
                             ev.button === 1 &&
                             window.open(url, "_blank")
                         }
+                        onLoad={() => setLoaded(true)}
                     />
                 </div>
             );
@@ -85,11 +87,15 @@ export default function Attachment({ attachment, hasContent }: Props) {
                         <AttachmentActions attachment={attachment} />
                         <video
                             src={url}
+                            width={metadata.width}
+                            height={metadata.height}
+                            className={classNames(loaded && styles.loaded)}
                             controls
                             onMouseDown={ev =>
                                 ev.button === 1 &&
                                 window.open(url, "_blank")
                             }
+                            onLoadedMetadata={() => setLoaded(true)}
                         />
                     </div>
                 </div>
diff --git a/src/components/common/messaging/attachments/AttachmentActions.tsx b/src/components/common/messaging/attachments/AttachmentActions.tsx
index 0248abd455f59bc78757cb4ba2501c85a21d491e..8b297f4becb000814764698ea314b911d53e7186 100644
--- a/src/components/common/messaging/attachments/AttachmentActions.tsx
+++ b/src/components/common/messaging/attachments/AttachmentActions.tsx
@@ -5,6 +5,7 @@ import { Attachment } from "revolt.js/dist/api/objects";
 import { determineFileSize } from '../../../../lib/fileSize';
 import { AppContext } from '../../../../context/revoltjs/RevoltClient';
 import { Download, LinkExternal, File, Headphone, Video } from '@styled-icons/boxicons-regular';
+import classNames from 'classnames';
 
 interface Props {
     attachment: Attachment;
@@ -24,17 +25,15 @@ export default function AttachmentActions({ attachment }: Props) {
     switch (metadata.type) {
         case 'Image':
             return (
-                <div className={styles.actions}>
-                    <div className={styles.info}>
-                        <span className={styles.filename}>{filename}</span>
-                        <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
-                    </div>
-                    <a href={open_url} target="_blank">
+                <div className={classNames(styles.actions, styles.imageAction)}>
+                    <span className={styles.filename}>{filename}</span>
+                    <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
+                    <a href={open_url} target="_blank" className={styles.iconType} >
                         <IconButton>
                             <LinkExternal size={24} />
                         </IconButton>
                     </a>
-                    <a href={download_url} download target="_blank">
+                    <a href={download_url} className={styles.downloadIcon} download target="_blank">
                         <IconButton>
                             <Download size={24} />
                         </IconButton>
@@ -43,13 +42,11 @@ export default function AttachmentActions({ attachment }: Props) {
             )
         case 'Audio':
             return (
-                <div className={styles.actions}>
-                    <Headphone size={24} />
-                    <div className={styles.info}>
-                        <span className={styles.filename}>{filename}</span>
-                        <span className={styles.filesize}>{filesize}</span>
-                    </div>
-                    <a href={download_url} download target="_blank">
+                <div className={classNames(styles.actions, styles.audioAction)}>
+                    <Headphone size={24} className={styles.iconType} />
+                    <span className={styles.filename}>{filename}</span>
+                    <span className={styles.filesize}>{filesize}</span>
+                    <a href={download_url} className={styles.downloadIcon} download target="_blank">
                         <IconButton>
                             <Download size={24} />
                         </IconButton>
@@ -58,13 +55,11 @@ export default function AttachmentActions({ attachment }: Props) {
             )
         case 'Video':
             return (
-                <div className={styles.actions}>
-                    <Video size={24} />
-                    <div className={styles.info}>
-                        <span className={styles.filename}>{filename}</span>
-                        <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
-                    </div>
-                    <a href={download_url} download target="_blank">
+                <div className={classNames(styles.actions, styles.videoAction)}>
+                    <Video size={24} className={styles.iconType} />
+                    <span className={styles.filename}>{filename}</span>
+                    <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
+                    <a href={download_url} className={styles.downloadIcon} download target="_blank">
                         <IconButton>
                             <Download size={24} />
                         </IconButton>
@@ -74,12 +69,10 @@ export default function AttachmentActions({ attachment }: Props) {
         default:
             return (
                 <div className={styles.actions}>
-                    <File size={24} />
-                    <div className={styles.info}>
-                        <span className={styles.filename}>{filename}</span>
-                        <span className={styles.filesize}>{filesize}</span>
-                    </div>
-                    <a href={download_url} download target="_blank">
+                    <File size={24} className={styles.iconType} />
+                    <span className={styles.filename}>{filename}</span>
+                    <span className={styles.filesize}>{filesize}</span>
+                    <a href={download_url} className={styles.downloadIcon} download target="_blank">
                         <IconButton>
                             <Download size={24} />
                         </IconButton>
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index 16e586bbcfe6f93e451479596ac3a81faad160e0..fc883f42aa86667b020c4b2f52c3d3a2cb5aa45f 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -3,12 +3,12 @@ import { useContext, useEffect } from "preact/hooks";
 import { Home, UserDetail, Wrench, Notepad } from "@styled-icons/boxicons-solid";
 
 import Category from '../../ui/Category';
+import { dispatch } from "../../../redux";
 import PaintCounter from "../../../lib/PaintCounter";
 import UserHeader from "../../common/user/UserHeader";
 import { Channels } from "revolt.js/dist/api/objects";
 import { connectState } from "../../../redux/connector";
 import ConnectionStatus from '../items/ConnectionStatus';
-import { WithDispatcher } from "../../../redux/reducers";
 import { Unreads } from "../../../redux/reducers/unreads";
 import ConditionalLink from "../../../lib/ConditionalLink";
 import { mapChannelWithUnread, useUnreads } from "./common";
@@ -23,7 +23,7 @@ import { useDMs, useForceUpdate, useUsers } from "../../../context/revoltjs/hook
 
 import placeholderSVG from "../items/placeholder.svg";
 
-type Props = WithDispatcher & {
+type Props = {
     unreads: Unreads;
 }
 
@@ -43,7 +43,7 @@ function HomeSidebar(props: Props) {
     useEffect(() => {
         if (!channel) return;
 
-        props.dispatcher({
+        dispatch({
             type: 'LAST_OPENED_SET',
             parent: 'home',
             child: channel
@@ -148,6 +148,5 @@ export default connectState(
             unreads: state.unreads
         };
     },
-    true,
     true
 );
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index 3dd7aff9d37dddba1ef063fa6414b0978d4d8e27..280dd8295c9a89677a58fde2c5740367d157d35f 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -2,7 +2,6 @@ import { Redirect, useParams } from "react-router";
 import { ChannelButton } from "../items/ButtonItem";
 import { Channels } from "revolt.js/dist/api/objects";
 import { Unreads } from "../../../redux/reducers/unreads";
-import { WithDispatcher } from "../../../redux/reducers";
 import { useChannels, useForceUpdate, useServer } from "../../../context/revoltjs/hooks";
 import { mapChannelWithUnread, useUnreads } from "./common";
 import ConnectionStatus from '../items/ConnectionStatus';
@@ -13,6 +12,7 @@ import { attachContextMenu } from 'preact-context-menu';
 import ServerHeader from "../../common/ServerHeader";
 import { useEffect } from "preact/hooks";
 import Category from "../../ui/Category";
+import { dispatch } from "../../../redux";
 import ConditionalLink from "../../../lib/ConditionalLink";
 import CollapsibleSection from "../../common/CollapsibleSection";
 
@@ -43,7 +43,7 @@ const ServerList = styled.div`
     }
 `;
 
-function ServerSidebar(props: Props & WithDispatcher) {
+function ServerSidebar(props: Props) {
     const { server: server_id, channel: channel_id } = useParams<{ server?: string, channel?: string }>();
     const ctx = useForceUpdate();
 
@@ -61,7 +61,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
     useEffect(() => {
         if (!channel_id) return;
 
-        props.dispatcher({
+        dispatch({
             type: 'LAST_OPENED_SET',
             parent: server_id!,
             child: channel_id!
@@ -130,6 +130,5 @@ export default connectState(
         return {
             unreads: state.unreads
         };
-    },
-    true
+    }
 );
diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts
index 2a681d10d2f025881182ebd31a29aa2ca9fcfdb7..f7ca164fbc47e1f2489952098294ec05366d894c 100644
--- a/src/components/navigation/left/common.ts
+++ b/src/components/navigation/left/common.ts
@@ -1,15 +1,15 @@
 import { Channel } from "revolt.js";
+import { dispatch } from "../../../redux";
 import { useLayoutEffect } from "preact/hooks";
-import { WithDispatcher } from "../../../redux/reducers";
 import { Unreads } from "../../../redux/reducers/unreads";
 import { HookContext, useForceUpdate } from "../../../context/revoltjs/hooks";
 
-type UnreadProps = WithDispatcher & {
+type UnreadProps = {
     channel: Channel;
     unreads: Unreads;
 }
 
-export function useUnreads({ channel, unreads, dispatcher }: UnreadProps, context?: HookContext) {
+export function useUnreads({ channel, unreads }: UnreadProps, context?: HookContext) {
     const ctx = useForceUpdate(context);
 
     useLayoutEffect(() => {
@@ -23,7 +23,7 @@ export function useUnreads({ channel, unreads, dispatcher }: UnreadProps, contex
             if (target.last_message) {
                 const message = typeof target.last_message === 'string' ? target.last_message : target.last_message._id;
                 if (!unread || (unread && message.localeCompare(unread) > 0)) {
-                    dispatcher({
+                    dispatch({
                         type: "UNREADS_MARK_READ",
                         channel: channel._id,
                         message
diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx
index 03287b15375b39de5919faf944c1111308b7e25b..40be8041dd0fcae9d11a6313c9a0a680bdadff88 100644
--- a/src/components/navigation/right/MemberSidebar.tsx
+++ b/src/components/navigation/right/MemberSidebar.tsx
@@ -2,7 +2,6 @@ import { Text } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 
 import { User } from "revolt.js";
-import Details from "../../../components/ui/Details";
 import Category from "../../ui/Category";
 import { useParams } from "react-router";
 import { UserButton } from "../items/ButtonItem";
@@ -15,6 +14,7 @@ import { AppContext, ClientStatus, StatusContext } from "../../../context/revolt
 import { HookContext, useChannel, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
 
 import placeholderSVG from "../items/placeholder.svg";
+import Preloader from "../../ui/Preloader";
 
 interface Props {
     ctx: HookContext
@@ -126,6 +126,7 @@ export function GroupMemberSidebar({ channel, ctx }: Props & { channel: Channels
 export function ServerMemberSidebar({ channel, ctx }: Props & { channel: Channels.TextChannel }) {
     const [members, setMembers] = useState<Servers.Member[] | undefined>(undefined);
     const users = useUsers(members?.map(x => x._id.user) ?? []).filter(x => typeof x !== 'undefined', ctx) as Users.User[];
+    const { openScreen } = useIntermediate();
     const status = useContext(StatusContext);
     const client = useContext(AppContext);
 
@@ -180,17 +181,16 @@ export function ServerMemberSidebar({ channel, ctx }: Props & { channel: Channel
                         </span>
                     }
                 />
-                {users.length === 0 && <img src={placeholderSVG} />}
+                {!members && <Preloader type="ring" />}
+                {members && users.length === 0 && <img src={placeholderSVG} />}
                 {users.map(
                     user =>
                         user && (
-                            // <LinkProfile user_id={user._id}>
-                                <UserButton
-                                    key={user._id}
-                                    user={user}
-                                    context={channel}
-                                />
-                            // </LinkProfile>
+                            <UserButton
+                                key={user._id}
+                                user={user}
+                                context={channel}
+                                onClick={() => openScreen({ id: 'profile', user_id: user._id })} />
                         )
                 )}
             </GenericSidebarList>
diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx
index 065d6952b12c40a3f557479ba3c8badab5ce63e1..8a9fc54e11c32954e398f3ab4826726eb64376ef 100644
--- a/src/context/Theme.tsx
+++ b/src/context/Theme.tsx
@@ -195,6 +195,9 @@ export const MONOSCAPE_FONTS: Record<MonoscapeFonts, { name: string, load: () =>
 export const FONT_KEYS = Object.keys(FONTS).sort();
 export const MONOSCAPE_FONT_KEYS = Object.keys(MONOSCAPE_FONTS).sort();
 
+export const DEFAULT_FONT = 'Open Sans';
+export const DEFAULT_MONO_FONT = 'Fira Code';
+
 // Generated from https://gitlab.insrt.uk/revolt/community/themes
 export const PRESETS: Record<string, Theme> = {
     light: {
@@ -281,13 +284,13 @@ function Theme({ children, options }: Props) {
 
     const root = document.documentElement.style;
     useEffect(() => {
-        const font = theme.font ?? 'Inter';
+        const font = theme.font ?? DEFAULT_FONT;
         root.setProperty('--font', `"${font}"`);
         FONTS[font].load();
     }, [ theme.font ]);
 
     useEffect(() => {
-        const font = theme.monoscapeFont ?? 'Fira Code';
+        const font = theme.monoscapeFont ?? DEFAULT_MONO_FONT;
         root.setProperty('--monoscape-font', `"${font}"`);
         MONOSCAPE_FONTS[font].load();
     }, [ theme.monoscapeFont ]);
diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx
index a975c101e85dffbde75601c92f0bbb8455ff7fde..91dc602f0bdc2724b4b55fdf271b6a4fe4edc0d4 100644
--- a/src/context/revoltjs/RevoltClient.tsx
+++ b/src/context/revoltjs/RevoltClient.tsx
@@ -2,12 +2,12 @@ import { openDB } from 'idb';
 import { Client } from "revolt.js";
 import { takeError } from "./util";
 import { createContext } from "preact";
+import { dispatch } from '../../redux';
 import { Children } from "../../types/Preact";
 import { useHistory } from 'react-router-dom';
 import { Route } from "revolt.js/dist/api/routes";
 import { connectState } from "../../redux/connector";
 import Preloader from "../../components/ui/Preloader";
-import { WithDispatcher } from "../../redux/reducers";
 import { AuthState } from "../../redux/reducers/auth";
 import { useEffect, useMemo, useState } from "preact/hooks";
 import { useIntermediate } from '../intermediate/Intermediate';
@@ -41,12 +41,12 @@ export const AppContext = createContext<Client>(null!);
 export const StatusContext = createContext<ClientStatus>(null!);
 export const OperationsContext = createContext<ClientOperations>(null!);
 
-type Props = WithDispatcher & {
+type Props = {
     auth: AuthState;
     children: Children;
 };
 
-function Context({ auth, children, dispatcher }: Props) {
+function Context({ auth, children }: Props) {
     const history = useHistory();
     const { openScreen } = useIntermediate();
     const [status, setStatus] = useState(ClientStatus.INIT);
@@ -94,7 +94,7 @@ function Context({ auth, children, dispatcher }: Props) {
                     const onboarding = await client.login(data);
                     setReconnectDisallowed(false);
                     const login = () =>
-                        dispatcher({
+                        dispatch({
                             type: "LOGIN",
                             session: client.session! // This [null assertion] is ok, we should have a session by now. - insert's words
                         });
@@ -114,10 +114,10 @@ function Context({ auth, children, dispatcher }: Props) {
                 }
             },
             logout: async shouldRequest => {
-                dispatcher({ type: "LOGOUT" });
+                dispatch({ type: "LOGOUT" });
 
                 client.reset();
-                dispatcher({ type: "RESET" });
+                dispatch({ type: "RESET" });
 
                 openScreen({ id: "none" });
                 setStatus(ClientStatus.READY);
@@ -145,7 +145,7 @@ function Context({ auth, children, dispatcher }: Props) {
         }
     }, [ client, auth.active ]);
 
-    useEffect(() => registerEvents({ operations, dispatcher }, setStatus, client), [ client ]);
+    useEffect(() => registerEvents({ operations }, setStatus, client), [ client ]);
 
     useEffect(() => {
         (async () => {
@@ -154,7 +154,7 @@ function Context({ auth, children, dispatcher }: Props) {
             }
 
             if (auth.active) {
-                dispatcher({ type: "QUEUE_FAIL_ALL" });
+                dispatch({ type: "QUEUE_FAIL_ALL" });
 
                 const active = auth.accounts[auth.active];
                 client.user = client.users.get(active.session.user_id);
@@ -226,6 +226,5 @@ export default connectState<{ children: Children }>(
             auth: state.auth,
             sync: state.sync
         };
-    },
-    true
+    }
 );
diff --git a/src/context/revoltjs/StateMonitor.tsx b/src/context/revoltjs/StateMonitor.tsx
index e333c7ac5cf942acad6d8018cbbf3eb917f54fb6..cc0d7616196af52da5a2233eb48ddb8400c96164 100644
--- a/src/context/revoltjs/StateMonitor.tsx
+++ b/src/context/revoltjs/StateMonitor.tsx
@@ -7,10 +7,10 @@ import { AppContext } from "./RevoltClient";
 import { Typing } from "../../redux/reducers/typing";
 import { useContext, useEffect } from "preact/hooks";
 import { connectState } from "../../redux/connector";
-import { WithDispatcher } from "../../redux/reducers";
 import { QueuedMessage } from "../../redux/reducers/queue";
+import { dispatch } from "../../redux";
 
-type Props = WithDispatcher & {
+type Props = {
     messages: QueuedMessage[];
     typing: Typing
 };
@@ -19,7 +19,7 @@ function StateMonitor(props: Props) {
     const client = useContext(AppContext);
 
     useEffect(() => {
-        props.dispatcher({
+        dispatch({
             type: 'QUEUE_DROP_ALL'
         });
     }, [ ]);
@@ -29,7 +29,7 @@ function StateMonitor(props: Props) {
             if (!msg.nonce) return;
             if (!props.messages.find(x => x.id === msg.nonce)) return;
 
-            props.dispatcher({
+            dispatch({
                 type: 'QUEUE_REMOVE',
                 nonce: msg.nonce
             });
@@ -47,7 +47,7 @@ function StateMonitor(props: Props) {
 
                 for (let user of users) {
                     if (+ new Date() > user.started + 5000) {
-                        props.dispatcher({
+                        dispatch({
                             type: 'TYPING_STOP',
                             channel,
                             user: user.id
@@ -73,6 +73,5 @@ export default connectState(
             messages: [...state.queue],
             typing: state.typing
         };
-    },
-    true
+    }
 );
diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx
index 2c1bbd83172f847ded161e46af4c2fea4bf039ed..f09ffb0d1ca7477e4c715085267f541b85f680e5 100644
--- a/src/context/revoltjs/SyncManager.tsx
+++ b/src/context/revoltjs/SyncManager.tsx
@@ -7,14 +7,14 @@ import { Language } from "../Locale";
 import { Sync } from "revolt.js/dist/api/objects";
 import { useContext, useEffect } from "preact/hooks";
 import { connectState } from "../../redux/connector";
-import { WithDispatcher } from "../../redux/reducers";
 import { Settings } from "../../redux/reducers/settings";
 import { Notifications } from "../../redux/reducers/notifications";
 import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";
 import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
 import { DEFAULT_ENABLED_SYNC, SyncData, SyncKeys, SyncOptions } from "../../redux/reducers/sync";
+import { dispatch } from "../../redux";
 
-type Props = WithDispatcher & {
+type Props = {
     settings: Settings,
     locale: Language,
     sync: SyncOptions,
@@ -54,7 +54,7 @@ function SyncManager(props: Props) {
             client
                 .syncFetchSettings(DEFAULT_ENABLED_SYNC.filter(x => !props.sync?.disabled?.includes(x)))
                 .then(data => {
-                    props.dispatcher({
+                    dispatch({
                         type: 'SYNC_UPDATE',
                         update: mapSync(data)
                     });
@@ -62,13 +62,13 @@ function SyncManager(props: Props) {
 
             client
                 .syncFetchUnreads()
-                .then(unreads => props.dispatcher({ type: 'UNREADS_SET', unreads }));
+                .then(unreads => dispatch({ type: 'UNREADS_SET', unreads }));
         }
     }, [ status ]);
 
     function syncChange(key: SyncKeys, data: any) {
         let timestamp = + new Date();
-        props.dispatcher({
+        dispatch({
             type: 'SYNC_SET_REVISION',
             key,
             timestamp
@@ -99,7 +99,7 @@ function SyncManager(props: Props) {
             if (packet.type === 'UserSettingsUpdate') {
                 let update: { [key in SyncKeys]?: [ number, SyncData[key] ] } = mapSync(packet.update, props.sync.revision);
 
-                props.dispatcher({
+                dispatch({
                     type: 'SYNC_UPDATE',
                     update
                 });
@@ -122,6 +122,5 @@ export default connectState(
             sync: state.sync,
             notifications: state.notifications
         };
-    },
-    true
+    }
 );
diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts
index 11732da5386353a119cd3c82f2113396619e1f70..319894689955981271fff384005383b3855eee60 100644
--- a/src/context/revoltjs/events.ts
+++ b/src/context/revoltjs/events.ts
@@ -1,11 +1,11 @@
 import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
-import { WithDispatcher } from "../../redux/reducers";
 import { Client, Message } from "revolt.js/dist";
 import {
     ClientOperations,
     ClientStatus
 } from "./RevoltClient";
 import { StateUpdater } from "preact/hooks";
+import { dispatch } from "../../redux";
 
 export var preventReconnect = false;
 let preventUntil = 0;
@@ -15,9 +15,8 @@ export function setReconnectDisallowed(allowed: boolean) {
 }
 
 export function registerEvents({
-    operations,
-    dispatcher
-}: { operations: ClientOperations } & WithDispatcher, setStatus: StateUpdater<ClientStatus>, client: Client) {
+    operations
+}: { operations: ClientOperations }, setStatus: StateUpdater<ClientStatus>, client: Client) {
     function attemptReconnect() {
         if (preventReconnect) return;
         function reconnect() {
@@ -47,7 +46,7 @@ export function registerEvents({
             switch (packet.type) {
                 case "ChannelStartTyping": {
                     if (packet.user === client.user?._id) return;
-                    dispatcher({
+                    dispatch({
                         type: "TYPING_START",
                         channel: packet.id,
                         user: packet.user
@@ -56,7 +55,7 @@ export function registerEvents({
                 }
                 case "ChannelStopTyping": {
                     if (packet.user === client.user?._id) return;
-                    dispatcher({
+                    dispatch({
                         type: "TYPING_STOP",
                         channel: packet.id,
                         user: packet.user
@@ -64,7 +63,7 @@ export function registerEvents({
                     break;
                 }
                 case "ChannelAck": {
-                    dispatcher({
+                    dispatch({
                         type: "UNREADS_MARK_READ",
                         channel: packet.id,
                         message: packet.message_id
@@ -76,7 +75,7 @@ export function registerEvents({
 
         message: (message: Message) => {
             if (message.mentions?.includes(client.user!._id)) {
-                dispatcher({
+                dispatch({
                     type: "UNREADS_MENTION",
                     channel: message.channel,
                     message: message._id
diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx
index 4b7a868fbafddfe66549bf99bb29fe683aa64cda..89f1a6bb87f906c6cde55a35b4ab5b7590a13251 100644
--- a/src/lib/ContextMenus.tsx
+++ b/src/lib/ContextMenus.tsx
@@ -10,7 +10,6 @@ import {
 } from "preact-context-menu";
 import { ChannelPermission, ServerPermission, UserPermission } from "revolt.js/dist/api/permissions";
 import { QueuedMessage } from "../redux/reducers/queue";
-import { WithDispatcher } from "../redux/reducers";
 import { useIntermediate } from "../context/intermediate/Intermediate";
 import { AppContext, ClientStatus, StatusContext } from "../context/revoltjs/RevoltClient";
 import { takeError } from "../context/revoltjs/util";
@@ -24,6 +23,7 @@ import { Cog } from "@styled-icons/boxicons-solid";
 import { getNotificationState, Notifications, NotificationState } from "../redux/reducers/notifications";
 import UserStatus from "../components/common/user/UserStatus";
 import IconButton from "../components/ui/IconButton";
+import { dispatch } from "../redux";
 
 interface ContextMenuData {
     user?: string;
@@ -81,7 +81,7 @@ type Action =
     | { action: "open_server_channel_settings", server: string, id: string }
     | { action: "set_notification_state", key: string, state?: NotificationState };
 
-type Props = WithDispatcher & {
+type Props = {
     notifications: Notifications
 };
 
@@ -110,7 +110,7 @@ function ContextMenus(props: Props) {
                             data.channel.channel_type === 'VoiceChannel') return;
 
                         let message = data.channel.channel_type === 'TextChannel' ? data.channel.last_message : data.channel.last_message._id;
-                        props.dispatcher({
+                        dispatch({
                             type: "UNREADS_MARK_READ",
                             channel: data.channel._id,
                             message
@@ -124,7 +124,7 @@ function ContextMenus(props: Props) {
                     {
                         const nonce = data.message.id;
                         const fail = (error: any) =>
-                            props.dispatcher({
+                            dispatch({
                                 type: "QUEUE_FAIL",
                                 nonce,
                                 error
@@ -141,7 +141,7 @@ function ContextMenus(props: Props) {
                             )
                             .catch(fail);
 
-                        props.dispatcher({
+                        dispatch({
                             type: "QUEUE_START",
                             nonce
                         });
@@ -150,7 +150,7 @@ function ContextMenus(props: Props) {
 
                 case "cancel_message":
                     {
-                        props.dispatcher({
+                        dispatch({
                             type: "QUEUE_REMOVE",
                             nonce: data.message.id
                         });
@@ -330,9 +330,9 @@ function ContextMenus(props: Props) {
                 case "set_notification_state": {
                     const { key, state } = data;
                     if (state) {
-                        props.dispatcher({ type: "NOTIFICATIONS_SET", key, state });
+                        dispatch({ type: "NOTIFICATIONS_SET", key, state });
                     } else {
-                        props.dispatcher({ type: "NOTIFICATIONS_REMOVE", key });
+                        dispatch({ type: "NOTIFICATIONS_REMOVE", key });
                     }
                     break;
                 }
@@ -760,6 +760,5 @@ export default connectState(
         return {
             notifications: state.notifications
         };
-    },
-    true
+    }
 );
diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx
index 737aae9fc7172cc91981f9a8e71cd71398b2fcdd..714e4086ec8a7bb04743dd6d2245236d5ee2886a 100644
--- a/src/pages/settings/panes/Appearance.tsx
+++ b/src/pages/settings/panes/Appearance.tsx
@@ -6,14 +6,13 @@ import Checkbox from "../../../components/ui/Checkbox";
 import ComboBox from "../../../components/ui/ComboBox";
 import InputBox from "../../../components/ui/InputBox";
 import { connectState } from "../../../redux/connector";
-import { WithDispatcher } from "../../../redux/reducers";
 import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
 import ColourSwatches from "../../../components/ui/ColourSwatches";
 import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
 import { useCallback, useContext, useEffect, useState } from "preact/hooks";
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
 import CollapsibleSection from "../../../components/common/CollapsibleSection";
-import { FONTS, FONT_KEYS, MONOSCAPE_FONTS, MONOSCAPE_FONT_KEYS, Theme, ThemeContext, ThemeOptions } from "../../../context/Theme";
+import { DEFAULT_FONT, DEFAULT_MONO_FONT, FONTS, FONT_KEYS, MONOSCAPE_FONTS, MONOSCAPE_FONT_KEYS, Theme, ThemeContext, ThemeOptions } from "../../../context/Theme";
 
 // @ts-ignore
 import pSBC from 'shade-blend-color';
@@ -25,25 +24,26 @@ import mutantSVG from '../assets/mutant_emoji.svg';
 import notoSVG from '../assets/noto_emoji.svg';
 import openmojiSVG from '../assets/openmoji_emoji.svg';
 import twemojiSVG from '../assets/twemoji_emoji.svg';
+import { dispatch } from "../../../redux";
 
 interface Props {
     settings: Settings;
 }
 
 // ! FIXME: code needs to be rewritten to fix jittering
-export function Component(props: Props & WithDispatcher) {
+export function Component(props: Props) {
     const theme = useContext(ThemeContext);
     const { writeClipboard, openScreen } = useIntermediate();
 
     function setTheme(theme: ThemeOptions) {
-        props.dispatcher({
+        dispatch({
             type: "SETTINGS_SET_THEME",
             theme
         });
     }
 
     function pushOverride(custom: Partial<Theme>) {
-        props.dispatcher({
+        dispatch({
             type: "SETTINGS_SET_THEME_OVERRIDE",
             custom
         });
@@ -58,7 +58,7 @@ export function Component(props: Props & WithDispatcher) {
 
     const emojiPack = props.settings.appearance?.emojiPack ?? 'mutant';
     function setEmojiPack(emojiPack: EmojiPacks) {
-        props.dispatcher({
+        dispatch({
             type: 'SETTINGS_SET_APPEARANCE',
             options: {
                 emojiPack
@@ -135,7 +135,7 @@ export function Component(props: Props & WithDispatcher) {
             <h3>
                 <Text id="app.settings.pages.appearance.font" />
             </h3>
-            <ComboBox value={theme.font} onChange={e => setTheme({ custom: { font: e.currentTarget.value as any } })}>
+            <ComboBox value={theme.font ?? DEFAULT_FONT} onChange={e => setTheme({ custom: { font: e.currentTarget.value as any } })}>
                 {
                     FONT_KEYS
                         .map(key =>
@@ -284,7 +284,7 @@ export function Component(props: Props & WithDispatcher) {
                 <h3>
                     <Text id="app.settings.pages.appearance.mono_font" />
                 </h3>
-                <ComboBox value={theme.monoscapeFont} onChange={e => setTheme({ custom: { monoscapeFont: e.currentTarget.value as any } })}>
+                <ComboBox value={theme.monoscapeFont ?? DEFAULT_MONO_FONT} onChange={e => setTheme({ custom: { monoscapeFont: e.currentTarget.value as any } })}>
                     {
                         MONOSCAPE_FONT_KEYS
                             .map(key =>
@@ -313,6 +313,5 @@ export const Appearance = connectState(
         return {
             settings: state.settings
         };
-    },
-    true
+    }
 );
diff --git a/src/pages/settings/panes/Experiments.tsx b/src/pages/settings/panes/Experiments.tsx
index 5d3d6ee95c9ca8cff1d66bb03ee953ecb6bd7ae0..c5dee4a9e303f48eda9334bd10aba88cfd3c6f0e 100644
--- a/src/pages/settings/panes/Experiments.tsx
+++ b/src/pages/settings/panes/Experiments.tsx
@@ -1,15 +1,15 @@
 import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
+import { dispatch } from "../../../redux";
 import Checkbox from "../../../components/ui/Checkbox";
 import { connectState } from "../../../redux/connector";
-import { WithDispatcher } from "../../../redux/reducers";
 import { AVAILABLE_EXPERIMENTS, ExperimentOptions } from "../../../redux/reducers/experiments";
 
 interface Props {
     options?: ExperimentOptions;
 }
 
-export function Component(props: Props & WithDispatcher) {
+export function Component(props: Props) {
     return (
         <div className={styles.experiments}>
             <h3>
@@ -20,12 +20,12 @@ export function Component(props: Props & WithDispatcher) {
                     key =>
                         <Checkbox
                             checked={(props.options?.enabled ?? []).indexOf(key) > -1}
-                            onChange={enabled => {
-                                props.dispatcher({
+                            onChange={enabled =>
+                                dispatch({
                                     type: enabled ? 'EXPERIMENTS_ENABLE' : 'EXPERIMENTS_DISABLE',
                                     key
-                                });
-                            }}
+                                })
+                            }
                         >
                             <Text id={`app.settings.pages.experiments.titles.${key}`} />
                             <p>
@@ -51,6 +51,5 @@ export const ExperimentsPage = connectState(
         return {
             options: state.experiments
         };
-    },
-    true
+    }
 );
diff --git a/src/pages/settings/panes/Languages.tsx b/src/pages/settings/panes/Languages.tsx
index e7ae6bf9f9b4fff785f09c69e80cd5454a70ab8a..077391a592367002bf611c480acb16ba71b08369 100644
--- a/src/pages/settings/panes/Languages.tsx
+++ b/src/pages/settings/panes/Languages.tsx
@@ -1,19 +1,19 @@
 import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
+import { dispatch } from "../../../redux";
 import Tip from "../../../components/ui/Tip";
 import Emoji from "../../../components/common/Emoji";
 import Checkbox from "../../../components/ui/Checkbox";
 import { connectState } from "../../../redux/connector";
-import { WithDispatcher } from "../../../redux/reducers";
 import { Language, LanguageEntry, Languages as Langs } from "../../../context/Locale";
 
-type Props = WithDispatcher & {
+type Props = {
     locale: Language;
 }
 
 type Key = [ string, LanguageEntry ];
 
-function Entry({ entry: [ x, lang ], locale, dispatcher }: { entry: Key } & Props) {
+function Entry({ entry: [ x, lang ], locale }: { entry: Key } & Props) {
     return (
         <Checkbox
             key={x}
@@ -21,7 +21,7 @@ function Entry({ entry: [ x, lang ], locale, dispatcher }: { entry: Key } & Prop
             checked={locale === x}
             onChange={v => {
                 if (v) {
-                    dispatcher({
+                    dispatch({
                         type: "SET_LOCALE",
                         locale: x as Language
                     });
@@ -80,6 +80,5 @@ export const Languages = connectState(
         return {
             locale: state.locale
         };
-    },
-    true
+    }
 );
diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx
index eccd0cead1fef22f4779d8a945aa2817d52bae6f..5098c85e9266c9291ea5b9c981e0ce9757c32aee 100644
--- a/src/pages/settings/panes/Notifications.tsx
+++ b/src/pages/settings/panes/Notifications.tsx
@@ -1,9 +1,9 @@
 import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
+import { dispatch } from "../../../redux";
 import defaultsDeep from "lodash.defaultsdeep";
 import Checkbox from "../../../components/ui/Checkbox";
 import { connectState } from "../../../redux/connector";
-import { WithDispatcher } from "../../../redux/reducers";
 import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { urlBase64ToUint8Array } from "../../../lib/conversion";
@@ -15,7 +15,7 @@ interface Props {
     options?: NotificationOptions;
 }
 
-export function Component({ options, dispatcher }: Props & WithDispatcher) {
+export function Component({ options }: Props) {
     const client = useContext(AppContext);
     const { openScreen } = useIntermediate();
     const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
@@ -51,7 +51,7 @@ export function Component({ options, dispatcher }: Props & WithDispatcher) {
                         }
                     }
 
-                    dispatcher({
+                    dispatch({
                         type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
                         options: { desktopEnabled }
                     });
@@ -107,7 +107,7 @@ export function Component({ options, dispatcher }: Props & WithDispatcher) {
                     <Checkbox
                         checked={enabledSounds[key] ? true : false}
                         onChange={enabled =>
-                            dispatcher({
+                            dispatch({
                                 type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
                                 options: {
                                     sounds: {
@@ -131,6 +131,5 @@ export const Notifications = connectState(
         return {
             options: state.settings.notification
         };
-    },
-    true
+    }
 );
diff --git a/src/pages/settings/panes/Sync.tsx b/src/pages/settings/panes/Sync.tsx
index 2055016b2c89dd208841822527e5e992db511fb5..20d51136818b1852067c9c0967bb77991bcceaef 100644
--- a/src/pages/settings/panes/Sync.tsx
+++ b/src/pages/settings/panes/Sync.tsx
@@ -1,15 +1,15 @@
 import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
+import { dispatch } from "../../../redux";
 import Checkbox from "../../../components/ui/Checkbox";
 import { connectState } from "../../../redux/connector";
-import { WithDispatcher } from "../../../redux/reducers";
 import { SyncKeys, SyncOptions } from "../../../redux/reducers/sync";
 
 interface Props {
     options?: SyncOptions;
 }
 
-export function Component(props: Props & WithDispatcher) {
+export function Component(props: Props) {
     return (
         <div className={styles.notifications}>
             <h3>
@@ -26,12 +26,12 @@ export function Component(props: Props & WithDispatcher) {
                         <Checkbox
                             checked={(props.options?.disabled ?? []).indexOf(key) === -1}
                             description={<Text id={`app.settings.pages.sync.descriptions.${key}`} />}
-                            onChange={enabled => {
-                                props.dispatcher({
+                            onChange={enabled =>
+                                dispatch({
                                     type: enabled ? 'SYNC_ENABLE_KEY' : 'SYNC_DISABLE_KEY',
                                     key
-                                });
-                            }}
+                                })
+                            }
                         >
                             <Text id={`app.settings.pages.${title}`} />
                         </Checkbox>
@@ -47,6 +47,5 @@ export const Sync = connectState(
         return {
             options: state.sync
         };
-    },
-    true
+    }
 );
diff --git a/src/redux/State.tsx b/src/redux/State.tsx
index c7f8575df4170e1ea31aa8c9dcbce0286ad6b6a9..2b3576ff463f5aef876a5963c455bd5f94d17215 100644
--- a/src/redux/State.tsx
+++ b/src/redux/State.tsx
@@ -1,7 +1,7 @@
-import { store } from ".";
 import localForage from "localforage";
 import { Provider } from "react-redux";
 import { Children } from "../types/Preact";
+import { dispatch, State, store } from ".";
 import { useEffect, useState } from "preact/hooks";
 
 interface Props {
@@ -15,7 +15,7 @@ export default function State(props: Props) {
         localForage.getItem("state")
             .then(state => {
                 if (state !== null) {
-                    store.dispatch({ type: "__INIT", state });
+                    dispatch({ type: "__INIT", state: state as State });
                 }
                 
                 setLoaded(true);
diff --git a/src/redux/connector.tsx b/src/redux/connector.tsx
index 06ee1d91a6163a22c9947f43d5f6121065075ac3..6d9075cabcc0dd6891450442685edb1d5f3699c8 100644
--- a/src/redux/connector.tsx
+++ b/src/redux/connector.tsx
@@ -8,16 +8,8 @@ import { connect, ConnectedComponent } from "react-redux";
 export function connectState<T>(
     component: (props: any) => h.JSX.Element | null,
     mapKeys: (state: State, props: T) => any,
-    useDispatcher?: boolean,
     memoize?: boolean
 ): ConnectedComponent<(props: any) => h.JSX.Element | null, T> {
-    let c = (
-        useDispatcher
-            ? connect(mapKeys, (dispatcher) => {
-                  return { dispatcher };
-              })
-            : connect(mapKeys)
-    )(component);
-    
+    let c = connect(mapKeys)(component);
     return memoize ? memo(c) : c;
 }
diff --git a/src/redux/index.ts b/src/redux/index.ts
index 4bee0dcd667006f406e247a19cb1e8d3c7f1ab35..e57d60ea1cc47d64e1b785bb60e060b73b20e389 100644
--- a/src/redux/index.ts
+++ b/src/redux/index.ts
@@ -1,6 +1,6 @@
 import { createStore } from "redux";
-import rootReducer from "./reducers";
 import localForage from "localforage";
+import rootReducer, { Action } from "./reducers";
 
 import { Core } from "revolt.js/dist/api/objects";
 import { Typing } from "./reducers/typing";
@@ -77,3 +77,7 @@ store.subscribe(() => {
         sectionToggle
     });
 });
+
+export function dispatch(action: Action) {
+    store.dispatch(action);
+}
diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts
index baf217dcf1dcc9c2252104e10f25309c5914b785..15b7e36294bb0e4a0e21592e5353ad1e241edd9a 100644
--- a/src/redux/reducers/index.ts
+++ b/src/redux/reducers/index.ts
@@ -47,8 +47,6 @@ export type Action =
     | SectionToggleAction
     | { type: "__INIT"; state: State };
 
-export type WithDispatcher = { dispatcher: (action: Action) => void };
-
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 export function filter(obj: any, keys: string[]) {
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/yarn.lock b/yarn.lock
index 48f54160cb7eec8fdee682dcfe449bb9dd417660..6fec18642ef22fc373748e1f54afa9d80e866547 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,7 +9,7 @@
   dependencies:
     "@babel/highlight" "^7.10.4"
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.14.5":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
   integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
@@ -21,6 +21,28 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.5.tgz#8ef4c18e58e801c5c95d3c1c0f2874a2680fadea"
   integrity sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==
 
+"@babel/core@7.13.10":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559"
+  integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==
+  dependencies:
+    "@babel/code-frame" "^7.12.13"
+    "@babel/generator" "^7.13.9"
+    "@babel/helper-compilation-targets" "^7.13.10"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helpers" "^7.13.10"
+    "@babel/parser" "^7.13.10"
+    "@babel/template" "^7.12.13"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
+    convert-source-map "^1.7.0"
+    debug "^4.1.0"
+    gensync "^1.0.0-beta.2"
+    json5 "^2.1.2"
+    lodash "^4.17.19"
+    semver "^6.3.0"
+    source-map "^0.5.0"
+
 "@babel/core@^7.11.1", "@babel/core@^7.9.6":
   version "7.14.6"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab"
@@ -42,7 +64,16 @@
     semver "^6.3.0"
     source-map "^0.5.0"
 
-"@babel/generator@^7.14.5":
+"@babel/generator@7.13.9":
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
+  integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
+  dependencies:
+    "@babel/types" "^7.13.0"
+    jsesc "^2.5.1"
+    source-map "^0.5.0"
+
+"@babel/generator@^7.13.0", "@babel/generator@^7.13.9", "@babel/generator@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785"
   integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==
@@ -66,7 +97,7 @@
     "@babel/helper-explode-assignable-expression" "^7.14.5"
     "@babel/types" "^7.14.5"
 
-"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5":
+"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf"
   integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==
@@ -117,7 +148,7 @@
   dependencies:
     "@babel/types" "^7.14.5"
 
-"@babel/helper-function-name@^7.14.5":
+"@babel/helper-function-name@^7.12.13", "@babel/helper-function-name@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4"
   integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==
@@ -154,7 +185,7 @@
   dependencies:
     "@babel/types" "^7.14.5"
 
-"@babel/helper-module-transforms@^7.14.5":
+"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e"
   integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==
@@ -213,14 +244,14 @@
   dependencies:
     "@babel/types" "^7.14.5"
 
-"@babel/helper-split-export-declaration@^7.14.5":
+"@babel/helper-split-export-declaration@^7.12.13", "@babel/helper-split-export-declaration@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a"
   integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==
   dependencies:
     "@babel/types" "^7.14.5"
 
-"@babel/helper-validator-identifier@^7.14.5":
+"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8"
   integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==
@@ -240,7 +271,7 @@
     "@babel/traverse" "^7.14.5"
     "@babel/types" "^7.14.5"
 
-"@babel/helpers@^7.14.6":
+"@babel/helpers@^7.13.10", "@babel/helpers@^7.14.6":
   version "7.14.6"
   resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635"
   integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==
@@ -258,6 +289,16 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
+"@babel/parser@7.13.10":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.10.tgz#8f8f9bf7b3afa3eabd061f7a5bcdf4fec3c48409"
+  integrity sha512-0s7Mlrw9uTWkYua7xWr99Wpk2bnGa0ANleKfksYAES8LpWH4gW1OUr42vqKNf0us5UQNfru2wPqMqRITzq/SIQ==
+
+"@babel/parser@^7.13.0", "@babel/parser@^7.13.10":
+  version "7.14.7"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595"
+  integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==
+
 "@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.7.0":
   version "7.14.6"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.6.tgz#d85cc68ca3cac84eae384c06f032921f5227f4b2"
@@ -843,7 +884,7 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/template@^7.14.5":
+"@babel/template@^7.12.13", "@babel/template@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
   integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==
@@ -852,6 +893,21 @@
     "@babel/parser" "^7.14.5"
     "@babel/types" "^7.14.5"
 
+"@babel/traverse@7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc"
+  integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==
+  dependencies:
+    "@babel/code-frame" "^7.12.13"
+    "@babel/generator" "^7.13.0"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-split-export-declaration" "^7.12.13"
+    "@babel/parser" "^7.13.0"
+    "@babel/types" "^7.13.0"
+    debug "^4.1.0"
+    globals "^11.1.0"
+    lodash "^4.17.19"
+
 "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.5.tgz#c111b0f58afab4fea3d3385a406f692748c59870"
@@ -867,7 +923,16 @@
     debug "^4.1.0"
     globals "^11.1.0"
 
-"@babel/types@^7.14.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
+"@babel/types@7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
+  integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.12.11"
+    lodash "^4.17.19"
+    to-fast-properties "^2.0.0"
+
+"@babel/types@^7.13.0", "@babel/types@^7.14.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff"
   integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==
@@ -1229,6 +1294,21 @@
   resolved "https://registry.yarnpkg.com/@traptitech/markdown-it-spoiler/-/markdown-it-spoiler-1.1.6.tgz#973e92045699551e2c9fb39bbd673ee48bc90b83"
   integrity sha512-tH/Fk1WMsnSuLpuRsXw8iHtdivoCEI5V08hQ7doVm6WmzAnBf/cUzyH9+GbOldPq9Hwv9v9tuy5t/MxmdNAGXg==
 
+"@trivago/prettier-plugin-sort-imports@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-2.0.2.tgz#62c52462220df6fb35815aaefeaa383fc2535ab1"
+  integrity sha512-esk6vplzXYwXQs079wBbKog4AFuZfxpJU+MygiijV0wbAibI0tEm+diFFhYP7B2lAaKKdU4+w+BW+McNZCw9HA==
+  dependencies:
+    "@babel/core" "7.13.10"
+    "@babel/generator" "7.13.9"
+    "@babel/parser" "7.13.10"
+    "@babel/traverse" "7.13.0"
+    "@babel/types" "7.13.0"
+    "@types/lodash" "4.14.168"
+    javascript-natural-sort "0.7.1"
+    lodash "4.17.21"
+    prettier "2.2.1"
+
 "@types/debug@^4.1.5":
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
@@ -1291,6 +1371,11 @@
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
   integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==
 
+"@types/lodash@4.14.168":
+  version "4.14.168"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
+  integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
+
 "@types/markdown-it@^12.0.2":
   version "12.0.2"
   resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.0.2.tgz#153e5477970ed2a47b2f619ed4ab66f870de8a04"
@@ -2732,6 +2817,11 @@ isomorphic-ws@^4.0.1:
   resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
   integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
 
+javascript-natural-sort@0.7.1:
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
+  integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=
+
 jest-worker@^26.2.1:
   version "26.6.2"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
@@ -2896,7 +2986,7 @@ lodash.truncate@^4.4.2:
   resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
   integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
 
-lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
+lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3233,6 +3323,11 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
+prettier@2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
+  integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
+
 prettier@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"