Skip to content

Commit

Permalink
feat(KNO-7785): use SWR in useConnectedSlackChannels hook (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmikolay authored Feb 4, 2025
1 parent 321c91f commit 12bc993
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 68 deletions.
7 changes: 7 additions & 0 deletions .changeset/dirty-parents-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@knocklabs/react": patch
---

Use SWR to update connected channels in `SlackChannelCombobox`

`SlackChannelCombobox` now uses [SWR](https://swr.vercel.app/) to retrieve and update connected Slack channels. There should be no change in the behavior of this component.
7 changes: 7 additions & 0 deletions .changeset/polite-dodos-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@knocklabs/react-core": patch
---

Use SWR in `useConnectedSlackChannels` hook

`useConnectedSlackChannels` now uses [SWR](https://swr.vercel.app/) under the hood. The returned array of connections (`data`) will now update optimistically when `updateConnectedChannels` is called.
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { useKnockSlackClient } from "..";
import { SlackChannelConnection } from "@knocklabs/client";
import { useCallback, useEffect, useState } from "react";
import { GenericData } from "@knocklabs/types";
import { useState } from "react";
import useSWR from "swr";

import { RecipientObject } from "../../..";
import { useKnockClient } from "../../core";
import { useTranslations } from "../../i18n";

const QUERY_KEY = "SLACK_CONNECTED_CHANNELS";

type UseConnectedSlackChannelsProps = {
slackChannelsRecipientObject: RecipientObject;
};

type UseConnectedSlackChannelOutput = {
type UseConnectedSlackChannelsOutput = {
data: SlackChannelConnection[] | null;
updateConnectedChannels: (
connectedChannels: SlackChannelConnection[],
Expand All @@ -22,83 +26,73 @@ type UseConnectedSlackChannelOutput = {

function useConnectedSlackChannels({
slackChannelsRecipientObject: { objectId, collection },
}: UseConnectedSlackChannelsProps): UseConnectedSlackChannelOutput {
}: UseConnectedSlackChannelsProps): UseConnectedSlackChannelsOutput {
const { t } = useTranslations();
const knock = useKnockClient();
const { connectionStatus, knockSlackChannelId } = useKnockSlackClient();
const [connectedChannels, setConnectedChannels] = useState<
null | SlackChannelConnection[]
>(null);

const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isUpdating, setIsUpdating] = useState(false);

const fetchAndSetConnectedChannels = useCallback(() => {
setIsLoading(true);
const getConnectedChannels = async () =>
await knock.objects.getChannelData({
collection,
objectId,
channelId: knockSlackChannelId,
});

getConnectedChannels()
.then((res) => {
if (res?.data?.connections) {
setConnectedChannels(res?.data?.connections);
} else {
setConnectedChannels([]);
}
setError(null);
setIsLoading(false);
})
.catch(() => {
setConnectedChannels([]);
setError(null);
setIsLoading(false);
});
}, [collection, knock.objects, knockSlackChannelId, objectId]);

useEffect(() => {
if (
connectionStatus === "connected" &&
!connectedChannels &&
!error &&
!isLoading
) {
fetchAndSetConnectedChannels();
}
}, [
connectedChannels,
fetchAndSetConnectedChannels,
const {
data: connectedChannels,
mutate,
isValidating,
isLoading,
error,
connectionStatus,
]);
} = useSWR<SlackChannelConnection[]>(
// Only fetch when Slack is connected
connectionStatus === "connected"
? [QUERY_KEY, knockSlackChannelId, collection, objectId]
: null,
async () => {
return knock.objects
.getChannelData({
collection,
objectId,
channelId: knockSlackChannelId,
})
.then((res) => res.data?.connections ?? [])
.catch(() => []);
},
{
onSuccess: () => {
setError(null);
},
},
);

const updateConnectedChannels = async (
channelsToSendToKnock: SlackChannelConnection[],
) => {
setIsUpdating(true);
try {
await knock.objects.setChannelData({
objectId,
collection,
channelId: knockSlackChannelId,
data: { connections: channelsToSendToKnock },
});
fetchAndSetConnectedChannels();
await mutate(
() =>
knock.objects
.setChannelData({
objectId,
collection,
channelId: knockSlackChannelId,
data: { connections: channelsToSendToKnock },
})
.then((res) => (res as GenericData).data?.connections ?? []),
{
populateCache: true,
revalidate: false,
optimisticData: channelsToSendToKnock,
},
);
} catch (_error) {
setError(t("slackChannelSetError") || "");
}
setIsUpdating(false);
};

return {
data: connectedChannels,
data: connectedChannels ?? null,
updateConnectedChannels,
updating: isUpdating,
loading: isLoading,
loading: isLoading || isValidating,
error,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ export const SlackChannelCombobox: FunctionComponent<
updating: connectedChannelsUpdating,
} = useConnectedSlackChannels({ slackChannelsRecipientObject });

const [currentConnectedChannels, setCurrentConnectedChannels] = useState<
SlackChannelConnection[] | null
>(null);

useEffect(() => {
if (comboboxListOpen) {
// Timeout to allow for the state to update and the component to re-render
Expand All @@ -101,7 +97,7 @@ export const SlackChannelCombobox: FunctionComponent<
}
}, [comboboxListOpen]);

useEffect(() => {
const currentConnectedChannels = useMemo<SlackChannelConnection[]>(() => {
// Used to make sure we're only showing currently available channels to select from.
// There are cases where a channel is "connected" in Knock, but it wouldn't be
// posting to it if the channel is private and the Slackbot doesn't belong to it,
Expand All @@ -110,12 +106,11 @@ export const SlackChannelCombobox: FunctionComponent<
slackChannels.map((channel) => [channel.id, channel]),
);

const channels =
return (
connectedChannels?.filter((connectedChannel) => {
return slackChannelsMap.has(connectedChannel.channel_id || "");
}) || [];

setCurrentConnectedChannels(channels);
}) || []
);
}, [connectedChannels, slackChannels]);

const inErrorState = useMemo(
Expand Down Expand Up @@ -220,15 +215,13 @@ export const SlackChannelCombobox: FunctionComponent<
(connectedChannel) => connectedChannel.channel_id !== channelId,
);

setCurrentConnectedChannels(channelsToSendToKnock);
updateConnectedChannels(channelsToSendToKnock);
} else {
const channelsToSendToKnock = [
...currentConnectedChannels,
{ channel_id: channelId } as SlackChannelConnection,
];

setCurrentConnectedChannels(channelsToSendToKnock);
updateConnectedChannels(channelsToSendToKnock);
}
};
Expand Down

0 comments on commit 12bc993

Please sign in to comment.