Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/red-owls-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"client-example": minor
"nextjs-example": minor
"slack-kit-example": minor
"@knocklabs/client": minor
"@knocklabs/expo": minor
"@knocklabs/react-core": minor
"@knocklabs/react-native": minor
"@knocklabs/react": minor
---

[JS] Support React 19 in React SDKs
10 changes: 6 additions & 4 deletions examples/client-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
"dependencies": {
"@knocklabs/client": "workspace:^",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^14.2.0",
"@testing-library/user-event": "^14.6.1",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.5.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^4.2.4",
"zustand": "^3.7.2"
"web-vitals": "^4.2.4"
},
"scripts": {
"dev": "PORT=3001 BROWSER=none react-scripts start",
Expand All @@ -37,5 +36,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/dom": "^10.4.0"
}
}
26 changes: 21 additions & 5 deletions examples/client-example/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Knock from "@knocklabs/client";
import { useEffect, useMemo } from "react";
import create from "zustand";
import { useEffect, useMemo, useState } from "react";

import "./App.css";

Expand All @@ -17,10 +16,10 @@ const useNotificationFeed = (knockClient, feedId) => {
auto_manage_socket_connection: true,
auto_manage_socket_connection_delay: 500,
});
const notificationStore = create(notificationFeed.store);

notificationFeed.fetch();

return [notificationFeed, notificationStore];
return [notificationFeed, notificationFeed.store];
}, [knockClient, feedId]);
};

Expand All @@ -29,6 +28,7 @@ function App() {
knockClient,
process.env.REACT_APP_KNOCK_CHANNEL_ID,
);
const [feedState, setFeedState] = useState(feedStore.getState());

useEffect(() => {
knockClient.preferences
Expand All @@ -41,6 +41,20 @@ function App() {
});
}, []);

// Consume the store
useEffect(() => {
// What to do on updates
const render = (state) => {
setFeedState(state);
};

// What to do on initial load
render(feedStore.getInitialState());

// Subscribe to updates
feedStore.subscribe(render);
}, [feedStore]);

useEffect(() => {
const teardown = feedClient.listenForUpdates();

Expand All @@ -59,7 +73,7 @@ function App() {
return () => teardown?.();
}, [feedClient]);

const { loading, items, pageInfo } = feedStore((state) => state);
const { loading, items, pageInfo } = feedState;

return (
<div className="App">
Expand All @@ -72,6 +86,8 @@ function App() {
<div key={item.id} className="feed-item">
ID: {item.id}
<br />
Has been read: {item.read_at ? "true" : "false"}
<br />
Actor ID: {item.actors?.[0]?.id}
<br />
Actor email: {item.actors?.[0]?.email}
Expand Down
7 changes: 4 additions & 3 deletions examples/client-example/src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";

import App from "./App";
import "./index.css";
import reportWebVitals from "./reportWebVitals";

ReactDOM.render(
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root"),
);

// If you want to start measuring performance in your app, pass a function
Expand Down
3 changes: 2 additions & 1 deletion examples/nextjs-example/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ KNOCK_SECRET_API_KEY=<Knock secret API key>

# Required for Knock's notification React feed component to render correctly
NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY=<Knock public API key>
NEXT_PUBLIC_KNOCK_FEED_CHANNEL_ID=<Knock in-app feed channel ID>
NEXT_PUBLIC_KNOCK_FEED_CHANNEL_ID=<Knock in-app feed channel ID for loading the feed>
NEXT_PUBLIC_WORKFLOW_KEY=<Knock workflow key that has the in-app channel, for sending notifications to this feed>

NEXT_PUBLIC_KNOCK_HOST=<Optional>
40 changes: 17 additions & 23 deletions examples/nextjs-example/components/NotificationToasts.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useToast } from "@chakra-ui/react";
import { type FeedItem } from "@knocklabs/client";
import { useKnockFeed } from "@knocklabs/react";
import { useCallback, useEffect } from "react";

import Toast from "./Toast";
import { toast } from "sonner";

const NotificationToasts = () => {
const { feedClient } = useKnockFeed();
const toast = useToast();

const onNotificationsReceived = useCallback(
({ items }: any) => {
({ items }: { items: FeedItem[] }) => {
// Whenever we receive a new notification from our real-time stream, show a toast
// (note here that we can receive > 1 items in a batch)
items.forEach((notification: any) => {
if (notification.data.showToast === false) return;

toast({
render: (props) => (
// @ts-expect-error - difference in status type
<Toast
{...props}
title={"New notification received"}
description={notification.blocks[0].rendered}
onClose={() => {
feedClient.markAsSeen(notification);
props.onClose();
}}
/>
),
position: "bottom-right",
items.forEach((notification) => {
if (notification.data?.showToast === false) return;

// You can access the Knock notification data
const description = notification.data?.message;

// Handle the notification however you want
toast.success("New Notification Received", {
description: description,
closeButton: true,
dismissible: true,
onDismiss: () => {
feedClient.markAsSeen(notification);
},
});
});
},
Expand Down
133 changes: 76 additions & 57 deletions examples/nextjs-example/components/SendNotificationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import {
Button,
Checkbox,
FormControl,
FormLabel,
Select,
Textarea,
} from "@chakra-ui/react";
import { FormEvent, useState } from "react";
import { Button } from "@telegraph/button";
import { Box, Stack } from "@telegraph/layout";
import { Select } from "@telegraph/select";
import { TextArea } from "@telegraph/textarea";
import { Text } from "@telegraph/typography";
import { type FormEvent, useState } from "react";

import { notify } from "../lib/api";

Expand All @@ -32,60 +29,82 @@ const SendNotificationForm = ({ userId, tenant }: Props) => {
setIsLoading(true);
await notify({ message, showToast, userId, tenant, templateType });
setIsLoading(false);

setMessage("");
(e.target as HTMLFormElement).reset();
};

return (
<form onSubmit={onSubmit}>
<FormControl mb={3}>
<FormLabel htmlFor="message" fontSize={14}>
Message
</FormLabel>
<Textarea
id="message"
name="message"
placeholder="Message to be shown in the notification"
size="sm"
onChange={(e) => setMessage(e.target.value)}
/>
</FormControl>
<FormControl mb={4}>
<FormLabel fontSize={14}>Template type</FormLabel>
<Select
mr={3}
size="sm"
value={templateType}
onChange={(e) => setTemplateType(e.target.value as TemplateType)}
<Stack direction="column" gap="4" marginTop="3">
<Box>
<Stack direction="column" gap="1">
<Text as="label" htmlFor="message" size="2">
Message
</Text>
<TextArea
as="textarea"
display="block"
id="message"
height="20"
name="message"
placeholder="Message to be shown in the notification"
size="2"
onChange={(e) => setMessage(e.target.value)}
/>
</Stack>
</Box>
<Box marginBottom="3">
<Stack direction="column" gap="1">
<Text as="label" size="2">
Template type
</Text>
<Box marginRight="2">
<Select.Root
size="2"
value={templateType}
onValueChange={(value) =>
setTemplateType(value as TemplateType)
}
>
<Select.Option value={TemplateType.Standard}>
Standard
</Select.Option>
<Select.Option value={TemplateType.SingleAction}>
Single-action
</Select.Option>
<Select.Option value={TemplateType.MultiAction}>
Multi-action
</Select.Option>
</Select.Root>
</Box>
</Stack>
</Box>
<Box marginBottom="3">
<Text as="label" size="2">
<Stack direction="row" alignItems="center">
<input
type="checkbox"
name="showToast"
checked={showToast}
onChange={(e) => setShowToast(e.target.checked)}
/>
<Text as="span" size="2" marginLeft="1">
Show a toast?
</Text>
</Stack>
</Text>
</Box>
<Button
type="submit"
variant="solid"
color="accent"
size="2"
disabled={message === ""}
state={isLoading ? "loading" : undefined}
>
<option value={TemplateType.Standard}>Standard</option>
<option value={TemplateType.SingleAction}>Single-action</option>
<option value={TemplateType.MultiAction}>Multi-action</option>
</Select>
</FormControl>
<FormControl mb={4}>
<FormLabel fontSize={14} display="flex" alignItems="center">
<Checkbox
name="showToast"
size="sm"
isChecked={showToast}
onChange={(e) => setShowToast(e.target.checked)}
mr={2}
/>{" "}
Show a toast?{" "}
</FormLabel>
</FormControl>

<Button
type="submit"
variant="solid"
colorScheme="gray"
size="sm"
isDisabled={message === ""}
isLoading={isLoading}
>
Send notification
</Button>
Send Notification
</Button>
</Stack>
</form>
);
};
Expand Down
Loading