Skip to content

Commit

Permalink
feat: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
TimMikeladze committed Dec 31, 2023
1 parent 86ac50f commit b16cd57
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Expand Up @@ -11,4 +11,5 @@ yarn.lock
.env.template
*.svg
*.ico
*.sql
*.sql
*.mov
126 changes: 125 additions & 1 deletion README.md
Expand Up @@ -72,12 +72,136 @@ export const dynamic = 'force-dynamic';
export const GET = (request: NextRequest) => NextRealtimeStreamHandler(request);
```

**app/realtime/page.ts**
Somewhere near the root of your app, inside of a React Server Component, render a `NextRealtimeStreamProvider`. This component will handle subscribing to the stream of tags and revalidating the cache.

**app/realtime/layout.ts**

```tsx
import { NextRealtimeStreamProvider } from 'next-realtime/react';
import { createRealtimeSessionId } from 'next-realtime/server';

import { revalidateTag } from 'next/cache';

import { ReactNode } from 'react';

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<NextRealtimeStreamProvider
revalidateTag={async (tag: string) => {
'use server';

revalidateTag(tag);
}}
sessionId={async () => {
'use server';

return createRealtimeSessionId();
}}
>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</NextRealtimeStreamProvider>
);
}
```

## 🛰️ Usage

1. Choose a cache tag whose data you want to make realtime. In this example, we'll use the tag `todos`.
2. Create an action that will fetch data and pass it through the `unstable_cache` function with the chosen cache tag.
3. Create a React Server Component that will fetch async data from the action and render it.
4. Create an action that will add new data to the database and invalidate the chosen cache tag.

**src/actions/getTodos.tsx**

```ts
'use server';

import { unstable_cache } from 'next/cache';
import { desc } from 'drizzle-orm';
import { getDb } from '../drizzle/getDb';
import { todoTable } from '../drizzle/schema';

export const getTodos = unstable_cache(
async () => {
const db = await getDb();

return db.select().from(todoTable).orderBy(desc(todoTable.createdAt));
},
[],
{
tags: ['todos'], // 👈 define a tag to make realtime
}
);
```

**src/components/TodoList.tsx**

```ts
import { getTodos } from '../actions/getTodos'; // 👈
import TodoCard from './TodoCard';

const TodoList = async () => { // 👈 async react component
const todos = await getTodos();

return (
<div className="container p-4">
<div className="space-y-4">
{todos.map((todo) => (
<TodoCard key={todo.id} todo={todo} />
))}
</div>
</div>
);
};

export default TodoList;
```

**src/actions/addTodo.ts**

```ts
'use server';

import { nanoid } from 'nanoid';
import { getRealtimeSessionId } from 'next-realtime/server';
import { revalidateRealtimeTag } from '../app/realtime/config'; // 👈
import { getDb } from '../drizzle/getDb';
import { todoTable } from '../drizzle/schema';

export const addTodo = async ({ text }: { text: string }) => {
if (!text || !text.trim().length) {
return;
}

const db = await getDb();

await db.insert(todoTable).values({
id: nanoid(),
text: text.trim(),
realtimeSessionId: getRealtimeSessionId(),
});

await revalidateRealtimeTag('todos'); // 👈
};
```

📺 ## Example

![](docs/example.mov)

<!-- TSDOC_START -->

<!-- TSDOC_END -->
Binary file added docs/example.mov
Binary file not shown.
24 changes: 21 additions & 3 deletions examples/next-realtime-example/src/app/layout.tsx
Expand Up @@ -4,6 +4,11 @@ import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

import { NextRealtimeStreamProvider } from 'next-realtime/react';
import { createRealtimeSessionId } from 'next-realtime/server';

import { revalidateTag } from 'next/cache';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
Expand All @@ -13,8 +18,21 @@ export const metadata: Metadata = {

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<NextRealtimeStreamProvider
revalidateTag={async (tag: string) => {
'use server';

revalidateTag(tag);
}}
sessionId={async () => {
'use server';

return createRealtimeSessionId();
}}
>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</NextRealtimeStreamProvider>
);
}
21 changes: 1 addition & 20 deletions examples/next-realtime-example/src/app/page.tsx
@@ -1,26 +1,7 @@
import { NextRealtimeStreamProvider } from 'next-realtime/react';
import { createRealtimeSessionId } from 'next-realtime/server';

import { revalidateTag } from 'next/cache';
import TodoList from '../components/TodoList';

export const dynamic = 'force-dynamic';

export default async function Home() {
return (
<NextRealtimeStreamProvider
revalidateTag={async (tag: string) => {
'use server';

revalidateTag(tag);
}}
sessionId={async () => {
'use server';

return createRealtimeSessionId();
}}
>
<TodoList />
</NextRealtimeStreamProvider>
);
return <TodoList />;
}

0 comments on commit b16cd57

Please sign in to comment.