From 411fac2527fcbce63439df16bf49294cdf5fd13b Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Thu, 29 Jul 2021 18:41:01 +0100
Subject: [PATCH] Remove useChannel

---
 src/components/common/AgeGate.tsx             |  7 +-
 src/components/common/AutoComplete.tsx        | 12 +--
 src/components/common/ChannelIcon.tsx         | 96 ++++++++++---------
 .../common/messaging/MessageBox.tsx           |  4 +-
 .../navigation/items/ButtonItem.tsx           | 20 ++--
 .../navigation/left/HomeSidebar.tsx           | 41 ++++----
 .../navigation/left/ServerListSidebar.tsx     | 13 ++-
 .../navigation/left/ServerSidebar.tsx         | 19 ++--
 src/components/navigation/left/common.ts      | 13 ++-
 .../navigation/right/MemberSidebar.tsx        | 37 +++----
 src/context/intermediate/Intermediate.tsx     | 12 +--
 src/context/intermediate/modals/Prompt.tsx    | 10 +-
 .../intermediate/popovers/ChannelInfo.tsx     | 21 ++--
 .../intermediate/popovers/UserProfile.tsx     | 19 ++--
 src/context/revoltjs/hooks.ts                 |  7 --
 src/context/revoltjs/util.tsx                 |  4 +-
 src/lib/ContextMenus.tsx                      | 38 ++++----
 src/mobx/index.ts                             |  4 +-
 src/pages/channels/Channel.tsx                | 21 ++--
 src/pages/channels/ChannelHeader.tsx          |  5 +-
 src/pages/channels/actions/HeaderActions.tsx  |  2 +-
 .../channels/messaging/ConversationStart.tsx  | 16 ++--
 src/pages/settings/ChannelSettings.tsx        | 12 ++-
 src/pages/settings/channel/Overview.tsx       | 16 ++--
 src/pages/settings/channel/Permissions.tsx    | 14 +--
 src/pages/settings/server/Categories.tsx      | 16 ++--
 src/pages/settings/server/Invites.tsx         | 16 ++--
 src/pages/settings/server/Overview.tsx        | 21 ++--
 28 files changed, 259 insertions(+), 257 deletions(-)

diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx
index 324005b..adfb365 100644
--- a/src/components/common/AgeGate.tsx
+++ b/src/components/common/AgeGate.tsx
@@ -1,10 +1,11 @@
+import { observer } from "mobx-react-lite";
 import { useHistory } from "react-router-dom";
-import { Channel } from "revolt.js";
 import styled from "styled-components";
 
 import { Text } from "preact-i18n";
 import { useState } from "preact/hooks";
 
+import { Channel } from "../../mobx";
 import { dispatch, getState } from "../../redux";
 
 import Button from "../ui/Button";
@@ -46,7 +47,7 @@ type Props = {
     channel: Channel;
 };
 
-export default function AgeGate(props: Props) {
+export default observer((props: Props) => {
     const history = useHistory();
     const [consent, setConsent] = useState(
         getState().sectionToggle["nsfw"] ?? false,
@@ -105,4 +106,4 @@ export default function AgeGate(props: Props) {
             </div>
         </Base>
     );
-}
+});
diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx
index faca2ff..e457cbf 100644
--- a/src/components/common/AutoComplete.tsx
+++ b/src/components/common/AutoComplete.tsx
@@ -5,7 +5,7 @@ import styled, { css } from "styled-components";
 
 import { StateUpdater, useState } from "preact/hooks";
 
-import { User } from "../../mobx";
+import { Channel, User } from "../../mobx";
 import { useData } from "../../mobx/State";
 
 import { useClient } from "../../context/revoltjs/RevoltClient";
@@ -28,7 +28,7 @@ export type AutoCompleteState =
             }
           | {
                 type: "channel";
-                matches: Channels.TextChannel[];
+                matches: Channel[];
             }
       ));
 
@@ -197,15 +197,13 @@ export function useAutoComplete(
             if (type === "channel" && searchClues?.channels) {
                 const channels = client.servers
                     .get(searchClues.channels.server)
-                    ?.channels.map((x) => client.channels.get(x))
-                    .filter(
-                        (x) => typeof x !== "undefined",
-                    ) as Channels.TextChannel[];
+                    ?.channels.map((x) => store.channels.get(x))
+                    .filter((x) => typeof x !== "undefined") as Channel[];
 
                 const matches = (
                     search.length > 0
                         ? channels.filter((channel) =>
-                              channel.name.toLowerCase().match(regex),
+                              channel.name!.toLowerCase().match(regex),
                           )
                         : channels
                 )
diff --git a/src/components/common/ChannelIcon.tsx b/src/components/common/ChannelIcon.tsx
index fb605c7..301b009 100644
--- a/src/components/common/ChannelIcon.tsx
+++ b/src/components/common/ChannelIcon.tsx
@@ -1,65 +1,67 @@
 import { Hash, VolumeFull } from "@styled-icons/boxicons-regular";
+import { observer } from "mobx-react-lite";
 import { Channels } from "revolt.js/dist/api/objects";
 
 import { useContext } from "preact/hooks";
 
+import { Channel } from "../../mobx";
+
 import { AppContext } from "../../context/revoltjs/RevoltClient";
 
 import { ImageIconBase, IconBaseProps } from "./IconBase";
 import fallback from "./assets/group.png";
 
-interface Props
-    extends IconBaseProps<
-        Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel
-    > {
+interface Props extends IconBaseProps<Channel> {
     isServerChannel?: boolean;
 }
 
-export default function ChannelIcon(
-    props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>,
-) {
-    const client = useContext(AppContext);
+export default observer(
+    (
+        props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>,
+    ) => {
+        const client = useContext(AppContext);
 
-    const {
-        size,
-        target,
-        attachment,
-        isServerChannel: server,
-        animate,
-        children,
-        as,
-        ...imgProps
-    } = props;
-    const iconURL = client.generateFileURL(
-        target?.icon ?? attachment,
-        { max_side: 256 },
-        animate,
-    );
-    const isServerChannel =
-        server ||
-        (target &&
-            (target.channel_type === "TextChannel" ||
-                target.channel_type === "VoiceChannel"));
+        const {
+            size,
+            target,
+            attachment,
+            isServerChannel: server,
+            animate,
+            children,
+            as,
+            ...imgProps
+        } = props;
+        const iconURL = client.generateFileURL(
+            target?.icon ?? attachment,
+            { max_side: 256 },
+            animate,
+        );
+        const isServerChannel =
+            server ||
+            (target &&
+                (target.channel_type === "TextChannel" ||
+                    target.channel_type === "VoiceChannel"));
 
-    if (typeof iconURL === "undefined") {
-        if (isServerChannel) {
-            if (target?.channel_type === "VoiceChannel") {
-                return <VolumeFull size={size} />;
+        if (typeof iconURL === "undefined") {
+            if (isServerChannel) {
+                if (target?.channel_type === "VoiceChannel") {
+                    return <VolumeFull size={size} />;
+                }
+                return <Hash size={size} />;
             }
-            return <Hash size={size} />;
         }
-    }
 
-    return (
-        // ! fixme: replace fallback with <picture /> + <source />
-        <ImageIconBase
-            {...imgProps}
-            width={size}
-            height={size}
-            loading="lazy"
-            aria-hidden="true"
-            square={isServerChannel}
-            src={iconURL ?? fallback}
-        />
-    );
-}
+        return (
+            // ! fixme: replace fallback with <picture /> + <source />
+            <ImageIconBase
+                {...imgProps}
+                width={size}
+                height={size}
+                loading="lazy"
+                aria-hidden="true"
+                square={isServerChannel}
+                src={iconURL ?? fallback}
+            />
+        );
+    },
+);
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index 0ebb500..2983295 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -1,7 +1,6 @@
 import { Send, HappyAlt, ShieldX } from "@styled-icons/boxicons-solid";
 import { Styleshare } from "@styled-icons/simple-icons";
 import Axios, { CancelTokenSource } from "axios";
-import { Channel } from "revolt.js";
 import { ChannelPermission } from "revolt.js/dist/api/permissions";
 import styled from "styled-components";
 import { ulid } from "ulid";
@@ -20,6 +19,7 @@ import {
     SMOOTH_SCROLL_ON_RECEIVE,
 } from "../../../lib/renderer/Singleton";
 
+import { Channel } from "../../../mobx";
 import { dispatch, getState } from "../../../redux";
 import { Reply } from "../../../redux/reducers/queue";
 
@@ -360,7 +360,7 @@ export default function MessageBox({ channel }: Props) {
         users: { type: "channel", id: channel._id },
         channels:
             channel.channel_type === "TextChannel"
-                ? { server: channel.server }
+                ? { server: channel.server! }
                 : undefined,
     });
 
diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx
index d19290d..734b522 100644
--- a/src/components/navigation/items/ButtonItem.tsx
+++ b/src/components/navigation/items/ButtonItem.tsx
@@ -10,7 +10,7 @@ import { Localizer, Text } from "preact-i18n";
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
 import { stopPropagation } from "../../../lib/stopPropagation";
 
-import { User } from "../../../mobx";
+import { Channel, User } from "../../../mobx";
 
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
 
@@ -34,8 +34,8 @@ type CommonProps = Omit<
 
 type UserProps = CommonProps & {
     user: User;
-    context?: Channels.Channel;
-    channel?: Channels.DirectMessageChannel;
+    context?: Channel;
+    channel?: Channel;
 };
 
 export const UserButton = observer((props: UserProps) => {
@@ -73,7 +73,7 @@ export const UserButton = observer((props: UserProps) => {
                 {
                     <div className={styles.subText}>
                         {channel?.last_message && alert ? (
-                            channel.last_message.short
+                            (channel.last_message as { short: string }).short
                         ) : (
                             <UserStatus user={user} />
                         )}
@@ -115,12 +115,12 @@ export const UserButton = observer((props: UserProps) => {
 });
 
 type ChannelProps = CommonProps & {
-    channel: Channels.Channel & { unread?: string };
+    channel: Channel & { unread?: string };
     user?: User;
     compact?: boolean;
 };
 
-export function ChannelButton(props: ChannelProps) {
+export const ChannelButton = observer((props: ChannelProps) => {
     const { active, alert, alertCount, channel, user, compact, ...divProps } =
         props;
 
@@ -153,12 +153,12 @@ export function ChannelButton(props: ChannelProps) {
                 {channel.channel_type === "Group" && (
                     <div className={styles.subText}>
                         {channel.last_message && alert ? (
-                            channel.last_message.short
+                            (channel.last_message as { short: string }).short
                         ) : (
                             <Text
                                 id="quantities.members"
-                                plural={channel.recipients.length}
-                                fields={{ count: channel.recipients.length }}
+                                plural={channel.recipients!.length}
+                                fields={{ count: channel.recipients!.length }}
                             />
                         )}
                     </div>
@@ -186,7 +186,7 @@ export function ChannelButton(props: ChannelProps) {
             </div>
         </div>
     );
-}
+});
 
 type ButtonProps = CommonProps & {
     onClick?: () => void;
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index e22d35b..d9cea63 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -43,10 +43,16 @@ const HomeSidebar = observer((props: Props) => {
     const { channel } = useParams<{ channel: string }>();
     const { openScreen } = useIntermediate();
 
-    const ctx = useForceUpdate();
-    const channels = useDMs(ctx);
+    const store = useData();
+    const channels = [...store.channels.values()]
+        .filter(
+            (x) =>
+                x.channel_type === "DirectMessage" ||
+                x.channel_type === "Group",
+        )
+        .map((x) => mapChannelWithUnread(x, props.unreads));
 
-    const obj = channels.find((x) => x?._id === channel);
+    const obj = store.channels.get(channel);
     if (channel && !obj) return <Redirect to="/" />;
     if (obj) useUnreads({ ...props, channel: obj });
 
@@ -60,12 +66,7 @@ const HomeSidebar = observer((props: Props) => {
         });
     }, [channel]);
 
-    const channelsArr = channels
-        .filter((x) => x.channel_type !== "SavedMessages")
-        .map((x) => mapChannelWithUnread(x, props.unreads));
-
-    const store = useData();
-    channelsArr.sort((b, a) => a.timestamp.localeCompare(b.timestamp));
+    channels.sort((b, a) => a.timestamp.localeCompare(b.timestamp));
 
     return (
         <GenericSidebarBase padding>
@@ -132,20 +133,22 @@ const HomeSidebar = observer((props: Props) => {
                         })
                     }
                 />
-                {channelsArr.length === 0 && (
+                {channels.length === 0 && (
                     <img src={placeholderSVG} loading="eager" />
                 )}
-                {channelsArr.map((x) => {
+                {channels.map((x) => {
                     let user;
-                    if (x.channel_type === "DirectMessage") {
-                        if (!x.active) return null;
+                    if (x.channel.channel_type === "DirectMessage") {
+                        if (!x.channel.active) return null;
 
-                        const recipient = client.channels.getRecipient(x._id);
+                        const recipient = client.channels.getRecipient(
+                            x.channel._id,
+                        );
                         user = store.users.get(recipient);
 
                         if (!user) {
                             console.warn(
-                                `Skipped DM ${x._id} because user was missing.`,
+                                `Skipped DM ${x.channel._id} because user was missing.`,
                             );
                             return null;
                         }
@@ -153,14 +156,14 @@ const HomeSidebar = observer((props: Props) => {
 
                     return (
                         <ConditionalLink
-                            active={x._id === channel}
-                            to={`/channel/${x._id}`}>
+                            active={x.channel._id === channel}
+                            to={`/channel/${x.channel._id}`}>
                             <ChannelButton
                                 user={user}
-                                channel={x}
+                                channel={x.channel}
                                 alert={x.unread}
                                 alertCount={x.alertCount}
-                                active={x._id === channel}
+                                active={x.channel._id === channel}
                             />
                         </ConditionalLink>
                     );
diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx
index 14f2432..bf8253f 100644
--- a/src/components/navigation/left/ServerListSidebar.tsx
+++ b/src/components/navigation/left/ServerListSidebar.tsx
@@ -186,16 +186,18 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
 
     const ctx = useForceUpdate();
     const activeServers = useServers(undefined, ctx) as Servers.Server[];
-    const channels = (useChannels(undefined, ctx) as Channel[]).map((x) =>
+    const channels = [...store.channels.values()].map((x) =>
         mapChannelWithUnread(x, unreads),
     );
 
-    const unreadChannels = channels.filter((x) => x.unread).map((x) => x._id);
+    const unreadChannels = channels
+        .filter((x) => x.unread)
+        .map((x) => x.channel?._id);
 
     const servers = activeServers.map((server) => {
         let alertCount = 0;
         for (const id of server.channels) {
-            const channel = channels.find((x) => x._id === id);
+            const channel = channels.find((x) => x.channel?._id === id);
             if (channel?.alertCount) {
                 alertCount += channel.alertCount;
             }
@@ -224,8 +226,9 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
     let alertCount = 0;
     for (const x of channels) {
         if (
-            ((x.channel_type === "DirectMessage" && x.active) ||
-                x.channel_type === "Group") &&
+            (x.channel?.channel_type === "DirectMessage"
+                ? x.channel?.active
+                : true) &&
             x.unread
         ) {
             homeUnread = "unread";
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index f4d5340..e21b33b 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -1,3 +1,4 @@
+import { observer } from "mobx-react-lite";
 import { Redirect, useParams } from "react-router";
 import { Channels } from "revolt.js/dist/api/objects";
 import styled from "styled-components";
@@ -8,6 +9,7 @@ import { useEffect } from "preact/hooks";
 import ConditionalLink from "../../../lib/ConditionalLink";
 import PaintCounter from "../../../lib/PaintCounter";
 
+import { useData } from "../../../mobx/State";
 import { dispatch } from "../../../redux";
 import { connectState } from "../../../redux/connector";
 import { Unreads } from "../../../redux/reducers/unreads";
@@ -53,7 +55,7 @@ const ServerList = styled.div`
     }
 `;
 
-function ServerSidebar(props: Props) {
+const ServerSidebar = observer((props: Props) => {
     const { server: server_id, channel: channel_id } =
         useParams<{ server?: string; channel?: string }>();
     const ctx = useForceUpdate();
@@ -61,13 +63,9 @@ function ServerSidebar(props: Props) {
     const server = useServer(server_id, ctx);
     if (!server) return <Redirect to="/" />;
 
-    const channels = (
-        useChannels(server.channels, ctx).filter(
-            (entry) => typeof entry !== "undefined",
-        ) as Readonly<Channels.TextChannel | Channels.VoiceChannel>[]
-    ).map((x) => mapChannelWithUnread(x, props.unreads));
+    const store = useData();
 
-    const channel = channels.find((x) => x?._id === channel_id);
+    const channel = channel_id ? store.channels.get(channel_id) : undefined;
     if (channel_id && !channel) return <Redirect to={`/server/${server_id}`} />;
     if (channel) useUnreads({ ...props, channel }, ctx);
 
@@ -85,7 +83,7 @@ function ServerSidebar(props: Props) {
     const elements = [];
 
     function addChannel(id: string) {
-        const entry = channels.find((x) => x._id === id);
+        const entry = store.channels.get(id);
         if (!entry) return;
 
         const active = channel?._id === entry._id;
@@ -98,7 +96,8 @@ function ServerSidebar(props: Props) {
                 <ChannelButton
                     channel={entry}
                     active={active}
-                    alert={entry.unread}
+                    // ! FIXME: pull it out directly
+                    alert={mapChannelWithUnread(entry, props.unreads).unread}
                     compact
                 />
             </ConditionalLink>
@@ -141,7 +140,7 @@ function ServerSidebar(props: Props) {
             <PaintCounter small />
         </ServerBase>
     );
-}
+});
 
 export default connectState(ServerSidebar, (state) => {
     return {
diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts
index f8539c2..6f60ba6 100644
--- a/src/components/navigation/left/common.ts
+++ b/src/components/navigation/left/common.ts
@@ -1,7 +1,6 @@
-import { Channel } from "revolt.js";
-
 import { useLayoutEffect } from "preact/hooks";
 
+import { Channel } from "../../../mobx";
 import { dispatch } from "../../../redux";
 import { Unreads } from "../../../redux/reducers/unreads";
 
@@ -63,12 +62,12 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) {
         channel.channel_type === "DirectMessage" ||
         channel.channel_type === "Group"
     ) {
-        last_message_id = channel.last_message?._id;
+        last_message_id = (channel.last_message as { _id: string })?._id;
     } else if (channel.channel_type === "TextChannel") {
-        last_message_id = channel.last_message;
+        last_message_id = channel.last_message as string;
     } else {
         return {
-            ...channel,
+            channel,
             unread: undefined,
             alertCount: undefined,
             timestamp: channel._id,
@@ -85,7 +84,7 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) {
                 unread = "mention";
             } else if (
                 u.last_id &&
-                last_message_id.localeCompare(u.last_id) > 0
+                (last_message_id as string).localeCompare(u.last_id) > 0
             ) {
                 unread = "unread";
             }
@@ -95,7 +94,7 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) {
     }
 
     return {
-        ...channel,
+        channel,
         timestamp: last_message_id ?? channel._id,
         unread,
         alertCount,
diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx
index 23d6555..8c2b813 100644
--- a/src/components/navigation/right/MemberSidebar.tsx
+++ b/src/components/navigation/right/MemberSidebar.tsx
@@ -8,6 +8,7 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"
 import { Text } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 
+import { Channel } from "../../../mobx";
 import { useData } from "../../../mobx/State";
 import { getState } from "../../../redux";
 
@@ -17,11 +18,7 @@ import {
     ClientStatus,
     StatusContext,
 } from "../../../context/revoltjs/RevoltClient";
-import {
-    HookContext,
-    useChannel,
-    useForceUpdate,
-} from "../../../context/revoltjs/hooks";
+import { HookContext } from "../../../context/revoltjs/hooks";
 
 import CollapsibleSection from "../../common/CollapsibleSection";
 import Button from "../../ui/Button";
@@ -34,27 +31,19 @@ import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
 import { UserButton } from "../items/ButtonItem";
 import { ChannelDebugInfo } from "./ChannelDebugInfo";
 
-interface Props {
-    ctx: HookContext;
-}
-
-export default function MemberSidebar(props: { channel?: Channels.Channel }) {
-    const ctx = useForceUpdate();
-    const { channel: cid } = useParams<{ channel: string }>();
-    const channel = props.channel ?? useChannel(cid, ctx);
-
+export default function MemberSidebar({ channel }: { channel?: Channel }) {
     switch (channel?.channel_type) {
         case "Group":
-            return <GroupMemberSidebar channel={channel} ctx={ctx} />;
+            return <GroupMemberSidebar channel={channel} />;
         case "TextChannel":
-            return <ServerMemberSidebar channel={channel} ctx={ctx} />;
+            return <ServerMemberSidebar channel={channel} />;
         default:
             return null;
     }
 }
 
 export const GroupMemberSidebar = observer(
-    ({ channel }: Props & { channel: Channels.GroupChannel }) => {
+    ({ channel }: { channel: Channel }) => {
         const { openScreen } = useIntermediate();
 
         const store = useData();
@@ -77,7 +66,7 @@ export const GroupMemberSidebar = observer(
         voiceParticipants.sort((a, b) => a.username.localeCompare(b.username));
     }*/
 
-        members.sort((a, b) => {
+        members?.sort((a, b) => {
             // ! FIXME: should probably rewrite all this code
             const l =
                 +(
@@ -141,21 +130,21 @@ export const GroupMemberSidebar = observer(
                                 text={
                                     <span>
                                         <Text id="app.main.categories.members" />{" "}
-                                        — {channel.recipients.length}
+                                        — {channel.recipients?.length ?? 0}
                                     </span>
                                 }
                             />
                         }>
-                        {members.length === 0 && (
+                        {members?.length === 0 && (
                             <img src={placeholderSVG} loading="eager" />
                         )}
-                        {members.map(
+                        {members?.map(
                             (user) =>
                                 user && (
                                     <UserButton
                                         key={user._id}
                                         user={user}
-                                        context={channel}
+                                        context={channel!}
                                         onClick={() =>
                                             openScreen({
                                                 id: "profile",
@@ -173,7 +162,7 @@ export const GroupMemberSidebar = observer(
 );
 
 export const ServerMemberSidebar = observer(
-    ({ channel }: Props & { channel: Channels.TextChannel }) => {
+    ({ channel }: { channel: Channel }) => {
         const [members, setMembers] = useState<Servers.Member[] | undefined>(
             undefined,
         );
@@ -193,7 +182,7 @@ export const ServerMemberSidebar = observer(
                 typeof members === "undefined"
             ) {
                 client.members
-                    .fetchMembers(channel.server)
+                    .fetchMembers(channel.server!)
                     .then((members) => setMembers(members));
             }
         }, [status]);
diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx
index 03b4125..72e7fe2 100644
--- a/src/context/intermediate/Intermediate.tsx
+++ b/src/context/intermediate/Intermediate.tsx
@@ -13,7 +13,7 @@ import { useContext, useEffect, useMemo, useState } from "preact/hooks";
 
 import { internalSubscribe } from "../../lib/eventEmitter";
 
-import { User } from "../../mobx";
+import { Channel, User } from "../../mobx";
 
 import { Action } from "../../components/ui/Modal";
 
@@ -34,15 +34,15 @@ export type Screen =
           actions: Action[];
       }
     | ({ id: "special_prompt" } & (
-          | { type: "leave_group"; target: Channels.GroupChannel }
-          | { type: "close_dm"; target: Channels.DirectMessageChannel }
+          | { type: "leave_group"; target: Channel }
+          | { type: "close_dm"; target: Channel }
           | { type: "leave_server"; target: Servers.Server }
           | { type: "delete_server"; target: Servers.Server }
-          | { type: "delete_channel"; target: Channels.TextChannel }
+          | { type: "delete_channel"; target: Channel }
           | { type: "delete_message"; target: Channels.Message }
           | {
                 type: "create_invite";
-                target: Channels.TextChannel | Channels.GroupChannel;
+                target: Channel;
             }
           | { type: "kick_member"; target: Servers.Server; user: User }
           | { type: "ban_member"; target: Servers.Server; user: User }
@@ -83,7 +83,7 @@ export type Screen =
     | { id: "image_viewer"; attachment?: Attachment; embed?: EmbedImage }
     | { id: "modify_account"; field: "username" | "email" | "password" }
     | { id: "profile"; user_id: string }
-    | { id: "channel_info"; channel_id: string }
+    | { id: "channel_info"; channel: Channel }
     | { id: "pending_requests"; users: User[] }
     | {
           id: "user_picker";
diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx
index 38096fc..ea9b276 100644
--- a/src/context/intermediate/modals/Prompt.tsx
+++ b/src/context/intermediate/modals/Prompt.tsx
@@ -9,7 +9,7 @@ import { useContext, useEffect, useState } from "preact/hooks";
 
 import { TextReact } from "../../../lib/i18n";
 
-import { User } from "../../../mobx";
+import { Channel, User } from "../../../mobx";
 import { useData } from "../../../mobx/State";
 
 import Message from "../../../components/common/messaging/Message";
@@ -55,15 +55,15 @@ export function PromptModal({
 }
 
 type SpecialProps = { onClose: () => void } & (
-    | { type: "leave_group"; target: Channels.GroupChannel }
-    | { type: "close_dm"; target: Channels.DirectMessageChannel }
+    | { type: "leave_group"; target: Channel }
+    | { type: "close_dm"; target: Channel }
     | { type: "leave_server"; target: Servers.Server }
     | { type: "delete_server"; target: Servers.Server }
-    | { type: "delete_channel"; target: Channels.TextChannel }
+    | { type: "delete_channel"; target: Channel }
     | { type: "delete_message"; target: Channels.Message }
     | {
           type: "create_invite";
-          target: Channels.TextChannel | Channels.GroupChannel;
+          target: Channel;
       }
     | { type: "kick_member"; target: Servers.Server; user: User }
     | { type: "ban_member"; target: Servers.Server; user: User }
diff --git a/src/context/intermediate/popovers/ChannelInfo.tsx b/src/context/intermediate/popovers/ChannelInfo.tsx
index 18a492a..3b15cd7 100644
--- a/src/context/intermediate/popovers/ChannelInfo.tsx
+++ b/src/context/intermediate/popovers/ChannelInfo.tsx
@@ -1,23 +1,23 @@
 import { X } from "@styled-icons/boxicons-regular";
+import { observer } from "mobx-react-lite";
 
 import styles from "./ChannelInfo.module.scss";
 
+import { Channel } from "../../../mobx";
+
 import Modal from "../../../components/ui/Modal";
 
 import Markdown from "../../../components/markdown/Markdown";
-import { useChannel, useForceUpdate } from "../../revoltjs/hooks";
+import { useClient } from "../../revoltjs/RevoltClient";
+import { useForceUpdate } from "../../revoltjs/hooks";
 import { getChannelName } from "../../revoltjs/util";
 
 interface Props {
-    channel_id: string;
+    channel: Channel;
     onClose: () => void;
 }
 
-export function ChannelInfo({ channel_id, onClose }: Props) {
-    const ctx = useForceUpdate();
-    const channel = useChannel(channel_id, ctx);
-    if (!channel) return null;
-
+export const ChannelInfo = observer(({ channel, onClose }: Props) => {
     if (
         channel.channel_type === "DirectMessage" ||
         channel.channel_type === "SavedMessages"
@@ -26,19 +26,20 @@ export function ChannelInfo({ channel_id, onClose }: Props) {
         return null;
     }
 
+    const client = useClient();
     return (
         <Modal visible={true} onClose={onClose}>
             <div className={styles.info}>
                 <div className={styles.header}>
-                    <h1>{getChannelName(ctx.client, channel, true)}</h1>
+                    <h1>{getChannelName(client, channel, true)}</h1>
                     <div onClick={onClose}>
                         <X size={36} />
                     </div>
                 </div>
                 <p>
-                    <Markdown content={channel.description} />
+                    <Markdown content={channel.description!} />
                 </p>
             </div>
         </Modal>
     );
-}
+});
diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx
index 0e0105d..b8c4e2d 100644
--- a/src/context/intermediate/popovers/UserProfile.tsx
+++ b/src/context/intermediate/popovers/UserProfile.tsx
@@ -27,11 +27,7 @@ import {
     StatusContext,
     useClient,
 } from "../../revoltjs/RevoltClient";
-import {
-    useChannels,
-    useForceUpdate,
-    useUserPermission,
-} from "../../revoltjs/hooks";
+import { useForceUpdate, useUserPermission } from "../../revoltjs/hooks";
 import { useIntermediate } from "../Intermediate";
 
 interface Props {
@@ -65,7 +61,6 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
     const [tab, setTab] = useState("profile");
 
     const ctx = useForceUpdate();
-    const channels = useChannels(undefined, ctx);
     const permissions = useUserPermission(client.user!._id, ctx);
 
     const store = useData();
@@ -77,6 +72,12 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
     const user = store.users.get(user_id)!;
     const users = mutual?.users.map((id) => store.users.get(id));
 
+    const mutualGroups = [...store.channels.values()].filter(
+        (channel) =>
+            channel?.channel_type === "Group" &&
+            channel.recipients!.includes(user_id),
+    );
+
     useLayoutEffect(() => {
         if (!user_id) return;
         if (typeof profile !== "undefined") setProfile(undefined);
@@ -111,12 +112,6 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
         }
     }, [profile, status]);
 
-    const mutualGroups = channels.filter(
-        (channel) =>
-            channel?.channel_type === "Group" &&
-            channel.recipients.includes(user_id),
-    );
-
     const backgroundURL =
         profile &&
         client.users.getBackgroundURL(profile, { width: 1000 }, true);
diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts
index e5ea687..99823c5 100644
--- a/src/context/revoltjs/hooks.ts
+++ b/src/context/revoltjs/hooks.ts
@@ -77,13 +77,6 @@ function useObject(
         : map.toArray();
 }
 
-export function useChannel(id?: string, context?: HookContext) {
-    if (typeof id === "undefined") return;
-    return useObject("channels", id, context) as
-        | Readonly<Channels.Channel>
-        | undefined;
-}
-
 export function useChannels(ids?: string[], context?: HookContext) {
     return useObject("channels", ids, context) as (
         | Readonly<Channels.Channel>
diff --git a/src/context/revoltjs/util.tsx b/src/context/revoltjs/util.tsx
index 98489e8..439dbf6 100644
--- a/src/context/revoltjs/util.tsx
+++ b/src/context/revoltjs/util.tsx
@@ -1,8 +1,10 @@
 import { Client } from "revolt.js";
-import { Channel, Message, User } from "revolt.js/dist/api/objects";
+import { Message } from "revolt.js/dist/api/objects";
 
 import { Text } from "preact-i18n";
 
+import { Channel } from "../../mobx";
+
 import { Children } from "../../types/Preact";
 
 export function takeError(error: any): string {
diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx
index 9cca0a2..d1e3bbe 100644
--- a/src/lib/ContextMenus.tsx
+++ b/src/lib/ContextMenus.tsx
@@ -34,7 +34,7 @@ import {
 import { Text } from "preact-i18n";
 import { useContext } from "preact/hooks";
 
-import { User } from "../mobx";
+import { Channel, User } from "../mobx";
 import { useData } from "../mobx/State";
 import { dispatch } from "../redux";
 import { connectState } from "../redux/connector";
@@ -53,7 +53,6 @@ import {
     useClient,
 } from "../context/revoltjs/RevoltClient";
 import {
-    useChannel,
     useChannelPermission,
     useForceUpdate,
     useServer,
@@ -86,7 +85,7 @@ type Action =
     | { action: "copy_id"; id: string }
     | { action: "copy_selection" }
     | { action: "copy_text"; content: string }
-    | { action: "mark_as_read"; channel: Channels.Channel }
+    | { action: "mark_as_read"; channel: Channel }
     | { action: "retry_message"; message: QueuedMessage }
     | { action: "cancel_message"; message: QueuedMessage }
     | { action: "mention"; user: string }
@@ -115,20 +114,17 @@ type Action =
     | { action: "create_channel"; target: Servers.Server }
     | {
           action: "create_invite";
-          target:
-              | Channels.GroupChannel
-              | Channels.TextChannel
-              | Channels.VoiceChannel;
+          target: Channel;
       }
-    | { action: "leave_group"; target: Channels.GroupChannel }
+    | { action: "leave_group"; target: Channel }
     | {
           action: "delete_channel";
-          target: Channels.TextChannel | Channels.VoiceChannel;
+          target: Channel;
       }
-    | { action: "close_dm"; target: Channels.DirectMessageChannel }
+    | { action: "close_dm"; target: Channel }
     | { action: "leave_server"; target: Servers.Server }
     | { action: "delete_server"; target: Servers.Server }
-    | { action: "open_notification_options"; channel: Channels.Channel }
+    | { action: "open_notification_options"; channel: Channel }
     | { action: "open_settings" }
     | { action: "open_channel_settings"; id: string }
     | { action: "open_server_settings"; id: string }
@@ -172,9 +168,10 @@ function ContextMenus(props: Props) {
                             return;
 
                         const message =
-                            data.channel.channel_type === "TextChannel"
+                            typeof data.channel.last_message === "string"
                                 ? data.channel.last_message
-                                : data.channel.last_message._id;
+                                : data.channel.last_message!._id;
+
                         dispatch({
                             type: "UNREADS_MARK_READ",
                             channel: data.channel._id,
@@ -529,8 +526,10 @@ function ContextMenus(props: Props) {
                         pushDivider();
                     }
 
-                    const channel = useChannel(cid, forceUpdate);
-                    const contextualChannel = useChannel(cxid, forceUpdate);
+                    const channel = cid ? store.channels.get(cid) : undefined;
+                    const contextualChannel = cxid
+                        ? store.channels.get(cxid)
+                        : undefined;
                     const targetChannel = channel ?? contextualChannel;
 
                     const user = uid ? store.users.get(uid) : undefined;
@@ -541,7 +540,7 @@ function ContextMenus(props: Props) {
                             ? targetChannel
                             : undefined;
                     const server = useServer(
-                        serverChannel ? serverChannel.server : sid,
+                        serverChannel ? serverChannel.server! : sid,
                         forceUpdate,
                     );
 
@@ -551,7 +550,10 @@ function ContextMenus(props: Props) {
                     const serverPermissions = server
                         ? useServerPermission(server._id, forceUpdate)
                         : serverChannel
-                        ? useServerPermission(serverChannel.server, forceUpdate)
+                        ? useServerPermission(
+                              serverChannel.server!,
+                              forceUpdate,
+                          )
                         : 0;
                     const userPermissions = user
                         ? useUserPermission(user._id, forceUpdate)
@@ -819,7 +821,7 @@ function ContextMenus(props: Props) {
                                         generateAction(
                                             {
                                                 action: "open_server_channel_settings",
-                                                server: channel.server,
+                                                server: channel.server!,
                                                 id: channel._id,
                                             },
                                             "open_channel_settings",
diff --git a/src/mobx/index.ts b/src/mobx/index.ts
index fdc1ae4..700c5b2 100644
--- a/src/mobx/index.ts
+++ b/src/mobx/index.ts
@@ -73,7 +73,7 @@ export class User {
 
 export class Channel {
     _id: string;
-    type: Channels.Channel["channel_type"];
+    channel_type: Channels.Channel["channel_type"];
 
     // Direct Message
     active: Nullable<boolean> = null;
@@ -98,7 +98,7 @@ export class Channel {
 
     constructor(data: Channels.Channel) {
         this._id = data._id;
-        this.type = data.channel_type;
+        this.channel_type = data.channel_type;
 
         switch (data.channel_type) {
             case "DirectMessage": {
diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx
index 3cd4b2b..9f40f8e 100644
--- a/src/pages/channels/Channel.tsx
+++ b/src/pages/channels/Channel.tsx
@@ -1,3 +1,4 @@
+import { observer } from "mobx-react-lite";
 import { useParams, useHistory } from "react-router-dom";
 import { Channels } from "revolt.js/dist/api/objects";
 import styled from "styled-components";
@@ -6,10 +7,10 @@ import { useState } from "preact/hooks";
 
 import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
 
+import { Channel as MobXChannel } from "../../mobx";
+import { useData } from "../../mobx/State";
 import { dispatch, getState } from "../../redux";
 
-import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks";
-
 import AgeGate from "../../components/common/AgeGate";
 import MessageBox from "../../components/common/messaging/MessageBox";
 import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom";
@@ -36,19 +37,19 @@ const ChannelContent = styled.div`
 `;
 
 export function Channel({ id }: { id: string }) {
-    const ctx = useForceUpdate();
-    const channel = useChannel(id, ctx);
-
+    const store = useData();
+    const channel = store.channels.get(id);
     if (!channel) return null;
 
     if (channel.channel_type === "VoiceChannel") {
         return <VoiceChannel channel={channel} />;
     }
+
     return <TextChannel channel={channel} />;
 }
 
 const MEMBERS_SIDEBAR_KEY = "sidebar_members";
-function TextChannel({ channel }: { channel: Channels.Channel }) {
+const TextChannel = observer(({ channel }: { channel: MobXChannel }) => {
     const [showMembers, setMembers] = useState(
         getState().sectionToggle[MEMBERS_SIDEBAR_KEY] ?? true,
     );
@@ -61,7 +62,9 @@ function TextChannel({ channel }: { channel: Channels.Channel }) {
             gated={
                 (channel.channel_type === "TextChannel" ||
                     channel.channel_type === "Group") &&
-                channel.name.includes("nsfw")
+                channel.name?.includes("nsfw")
+                    ? true
+                    : false
             }>
             <ChannelHeader
                 channel={channel}
@@ -96,9 +99,9 @@ function TextChannel({ channel }: { channel: Channels.Channel }) {
             </ChannelMain>
         </AgeGate>
     );
-}
+});
 
-function VoiceChannel({ channel }: { channel: Channels.Channel }) {
+function VoiceChannel({ channel }: { channel: MobXChannel }) {
     return (
         <>
             <ChannelHeader channel={channel} />
diff --git a/src/pages/channels/ChannelHeader.tsx b/src/pages/channels/ChannelHeader.tsx
index 69e44e1..f14ac51 100644
--- a/src/pages/channels/ChannelHeader.tsx
+++ b/src/pages/channels/ChannelHeader.tsx
@@ -2,14 +2,13 @@ import { At, Hash, Menu } from "@styled-icons/boxicons-regular";
 import { Notepad, Group } from "@styled-icons/boxicons-solid";
 import { observable } from "mobx";
 import { observer } from "mobx-react-lite";
-import { Channel } from "revolt.js";
 import styled from "styled-components";
 
 import { useContext } from "preact/hooks";
 
 import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
 
-import { User } from "../../mobx";
+import { Channel, User } from "../../mobx";
 import { useData } from "../../mobx/State";
 
 import { useIntermediate } from "../../context/intermediate/Intermediate";
@@ -131,7 +130,7 @@ export default observer(({ channel, toggleSidebar }: ChannelHeaderProps) => {
                                 onClick={() =>
                                     openScreen({
                                         id: "channel_info",
-                                        channel_id: channel._id,
+                                        channel,
                                     })
                                 }>
                                 <Markdown
diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx
index 647508a..b7eb775 100644
--- a/src/pages/channels/actions/HeaderActions.tsx
+++ b/src/pages/channels/actions/HeaderActions.tsx
@@ -41,7 +41,7 @@ export default function HeaderActions({
                         onClick={() =>
                             openScreen({
                                 id: "user_picker",
-                                omit: channel.recipients,
+                                omit: channel.recipients!,
                                 callback: async (users) => {
                                     for (const user of users) {
                                         await client.channels.addMember(
diff --git a/src/pages/channels/messaging/ConversationStart.tsx b/src/pages/channels/messaging/ConversationStart.tsx
index acaef3c..447f24f 100644
--- a/src/pages/channels/messaging/ConversationStart.tsx
+++ b/src/pages/channels/messaging/ConversationStart.tsx
@@ -1,8 +1,11 @@
+import { observer } from "mobx-react-lite";
 import styled from "styled-components";
 
 import { Text } from "preact-i18n";
 
-import { useChannel, useForceUpdate } from "../../../context/revoltjs/hooks";
+import { useData } from "../../../mobx/State";
+
+import { useClient } from "../../../context/revoltjs/RevoltClient";
 import { getChannelName } from "../../../context/revoltjs/util";
 
 const StartBase = styled.div`
@@ -24,17 +27,18 @@ interface Props {
     id: string;
 }
 
-export default function ConversationStart({ id }: Props) {
-    const ctx = useForceUpdate();
-    const channel = useChannel(id, ctx);
+export default observer(({ id }: Props) => {
+    const store = useData();
+    const client = useClient();
+    const channel = store.channels.get(id);
     if (!channel) return null;
 
     return (
         <StartBase>
-            <h1>{getChannelName(ctx.client, channel, true)}</h1>
+            <h1>{getChannelName(client, channel, true)}</h1>
             <h4>
                 <Text id="app.main.channel.start.group" />
             </h4>
         </StartBase>
     );
-}
+});
diff --git a/src/pages/settings/ChannelSettings.tsx b/src/pages/settings/ChannelSettings.tsx
index 4e12183..79015ad 100644
--- a/src/pages/settings/ChannelSettings.tsx
+++ b/src/pages/settings/ChannelSettings.tsx
@@ -3,7 +3,9 @@ import { Route, useHistory, useParams } from "react-router-dom";
 
 import { Text } from "preact-i18n";
 
-import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks";
+import { useData } from "../../mobx/State";
+
+import { useClient } from "../../context/revoltjs/RevoltClient";
 import { getChannelName } from "../../context/revoltjs/util";
 
 import Category from "../../components/ui/Category";
@@ -14,8 +16,10 @@ import Permissions from "./channel/Permissions";
 
 export default function ChannelSettings() {
     const { channel: cid } = useParams<{ channel: string }>();
-    const ctx = useForceUpdate();
-    const channel = useChannel(cid, ctx);
+
+    const store = useData();
+    const client = useClient();
+    const channel = store.channels.get(cid);
     if (!channel) return null;
     if (
         channel.channel_type === "SavedMessages" ||
@@ -49,7 +53,7 @@ export default function ChannelSettings() {
                     category: (
                         <Category
                             variant="uniform"
-                            text={getChannelName(ctx.client, channel, true)}
+                            text={getChannelName(client, channel, true)}
                         />
                     ),
                     id: "overview",
diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx
index b4a895e..cf1da07 100644
--- a/src/pages/settings/channel/Overview.tsx
+++ b/src/pages/settings/channel/Overview.tsx
@@ -1,3 +1,4 @@
+import { observer } from "mobx-react-lite";
 import { Channels } from "revolt.js/dist/api/objects";
 import styled, { css } from "styled-components";
 
@@ -6,6 +7,8 @@ import { useContext, useEffect, useState } from "preact/hooks";
 
 import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
 
+import { Channel } from "../../../mobx";
+
 import { FileUploader } from "../../../context/revoltjs/FileUploads";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 
@@ -13,10 +16,7 @@ import Button from "../../../components/ui/Button";
 import InputBox from "../../../components/ui/InputBox";
 
 interface Props {
-    channel:
-        | Channels.GroupChannel
-        | Channels.TextChannel
-        | Channels.VoiceChannel;
+    channel: Channel;
 }
 
 const Row = styled.div`
@@ -32,13 +32,13 @@ const Row = styled.div`
     }
 `;
 
-export default function Overview({ channel }: Props) {
+export default observer(({ channel }: Props) => {
     const client = useContext(AppContext);
 
-    const [name, setName] = useState(channel.name);
+    const [name, setName] = useState(channel.name ?? undefined);
     const [description, setDescription] = useState(channel.description ?? "");
 
-    useEffect(() => setName(channel.name), [channel.name]);
+    useEffect(() => setName(channel.name ?? undefined), [channel.name]);
     useEffect(
         () => setDescription(channel.description ?? ""),
         [channel.description],
@@ -127,4 +127,4 @@ export default function Overview({ channel }: Props) {
             </p>
         </div>
     );
-}
+});
diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx
index 963694a..c7fc235 100644
--- a/src/pages/settings/channel/Permissions.tsx
+++ b/src/pages/settings/channel/Permissions.tsx
@@ -1,8 +1,11 @@
+import { observer } from "mobx-react-lite";
 import { Channels } from "revolt.js/dist/api/objects";
 import { ChannelPermission } from "revolt.js/dist/api/permissions";
 
 import { useContext, useEffect, useState } from "preact/hooks";
 
+import { Channel } from "../../../mobx";
+
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 import { useServer } from "../../../context/revoltjs/hooks";
 
@@ -21,21 +24,18 @@ const DEFAULT_PERMISSION_DM =
     ChannelPermission.UploadFiles;
 
 interface Props {
-    channel:
-        | Channels.GroupChannel
-        | Channels.TextChannel
-        | Channels.VoiceChannel;
+    channel: Channel;
 }
 
 // ! FIXME: bad code :)
-export default function Permissions({ channel }: Props) {
+export default observer(({ channel }: Props) => {
     const [selected, setSelected] = useState("default");
     const client = useContext(AppContext);
 
     type R = { name: string; permissions: number };
     const roles: { [key: string]: R } = {};
     if (channel.channel_type !== "Group") {
-        const server = useServer(channel.server);
+        const server = useServer(channel.server!);
         const a = server?.roles ?? {};
         for (const b of Object.keys(a)) {
             roles[b] = {
@@ -110,4 +110,4 @@ export default function Permissions({ channel }: Props) {
             </Button>
         </div>
     );
-}
+});
diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx
index 8f35bcf..6e75222 100644
--- a/src/pages/settings/server/Categories.tsx
+++ b/src/pages/settings/server/Categories.tsx
@@ -1,5 +1,6 @@
 import { XCircle } from "@styled-icons/boxicons-regular";
 import isEqual from "lodash.isequal";
+import { observer } from "mobx-react-lite";
 import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
 import { Route } from "revolt.js/dist/api/routes";
 import { ulid } from "ulid";
@@ -8,8 +9,9 @@ import styles from "./Panes.module.scss";
 import { Text } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 
+import { useData } from "../../../mobx/State";
+
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
-import { useChannels } from "../../../context/revoltjs/hooks";
 
 import ChannelIcon from "../../../components/common/ChannelIcon";
 import UserIcon from "../../../components/common/user/UserIcon";
@@ -25,12 +27,12 @@ interface Props {
 }
 
 // ! FIXME: really bad code
-export function Categories({ server }: Props) {
+export const Categories = observer(({ server }: Props) => {
     const client = useContext(AppContext);
-    const channels = useChannels(server.channels) as (
-        | Channels.TextChannel
-        | Channels.VoiceChannel
-    )[];
+    const store = useData();
+    const channels = server.channels
+        .map((id) => store.channels.get(id)!)
+        .filter((x) => typeof x !== "undefined");
 
     const [cats, setCats] = useState<Servers.Category[]>(
         server.categories ?? [],
@@ -150,4 +152,4 @@ export function Categories({ server }: Props) {
             })}
         </div>
     );
-}
+});
diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx
index 1708d85..6e07244 100644
--- a/src/pages/settings/server/Invites.tsx
+++ b/src/pages/settings/server/Invites.tsx
@@ -9,7 +9,6 @@ import { useEffect, useState } from "preact/hooks";
 import { useData } from "../../../mobx/State";
 
 import { useClient } from "../../../context/revoltjs/RevoltClient";
-import { useChannels, useForceUpdate } from "../../../context/revoltjs/hooks";
 import { getChannelName } from "../../../context/revoltjs/util";
 
 import UserIcon from "../../../components/common/user/UserIcon";
@@ -26,14 +25,15 @@ export const Invites = observer(({ server }: Props) => {
         InvitesNS.ServerInvite[] | undefined
     >(undefined);
 
-    const ctx = useForceUpdate();
-    const channels = useChannels(invites?.map((x) => x.channel) ?? [], ctx);
-
     const store = useData();
+    const client = useClient();
     const users = invites?.map((invite) => store.users.get(invite.creator));
+    const channels = invites?.map((invite) =>
+        store.channels.get(invite.channel),
+    );
 
     useEffect(() => {
-        ctx.client.servers
+        client.servers
             .fetchInvites(server._id)
             .then((invites) => setInvites(invites));
     }, []);
@@ -57,7 +57,7 @@ export const Invites = observer(({ server }: Props) => {
             {typeof invites === "undefined" && <Preloader type="ring" />}
             {invites?.map((invite, index) => {
                 const creator = users![index];
-                const channel = channels.find((x) => x?._id === invite.channel);
+                const channel = channels![index];
 
                 return (
                     <div
@@ -72,14 +72,14 @@ export const Invites = observer(({ server }: Props) => {
                         </span>
                         <span>
                             {channel && creator
-                                ? getChannelName(ctx.client, channel, true)
+                                ? getChannelName(client, channel, true)
                                 : "#??"}
                         </span>
                         <IconButton
                             onClick={async () => {
                                 setDelete([...deleting, invite._id]);
 
-                                await ctx.client.deleteInvite(invite._id);
+                                await client.deleteInvite(invite._id);
 
                                 setInvites(
                                     invites?.filter(
diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx
index e92e952..ba25104 100644
--- a/src/pages/settings/server/Overview.tsx
+++ b/src/pages/settings/server/Overview.tsx
@@ -1,4 +1,5 @@
 import isEqual from "lodash.isequal";
+import { observer } from "mobx-react-lite";
 import { Servers, Server } from "revolt.js/dist/api/objects";
 
 import styles from "./Panes.module.scss";
@@ -7,6 +8,8 @@ import { useContext, useEffect, useState } from "preact/hooks";
 
 import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
 
+import { useData } from "../../../mobx/State";
+
 import { FileUploader } from "../../../context/revoltjs/FileUploads";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 import { getChannelName } from "../../../context/revoltjs/util";
@@ -19,8 +22,9 @@ interface Props {
     server: Servers.Server;
 }
 
-export function Overview({ server }: Props) {
+export const Overview = observer(({ server }: Props) => {
     const client = useContext(AppContext);
+    const store = useData();
 
     const [name, setName] = useState(server.name);
     const [description, setDescription] = useState(server.description ?? "");
@@ -170,15 +174,14 @@ export function Overview({ server }: Props) {
                         <option value="disabled">
                             <Text id="general.disabled" />
                         </option>
-                        {server.channels.map((id) => {
-                            const channel = client.channels.get(id);
-                            if (!channel) return null;
-                            return (
-                                <option value={id}>
+                        {server.channels
+                            .map((id) => store.channels.get(id)!)
+                            .filter((x) => typeof x !== "undefined")
+                            .map((channel) => (
+                                <option value={channel._id}>
                                     {getChannelName(client, channel, true)}
                                 </option>
-                            );
-                        })}
+                            ))}
                     </ComboBox>
                 </p>
             ))}
@@ -190,4 +193,4 @@ export function Overview({ server }: Props) {
             </p>
         </div>
     );
-}
+});
-- 
GitLab