Examples
Practical examples using the Unchange SDK
Basic Setup
import { Unchange } from 'unchange-sdk';
// Initialize the client
const client = new Unchange({
apiKey: process.env.UNCHANGE_API_KEY!,
projectId: process.env.UNCHANGE_PROJECT_ID!,
});Display Latest Changelog to User
Show the latest changelog to a user and track whether they've seen it:
async function showLatestChangelog(userId: string) {
try {
const changelog = await client.changelogs.getLatestForUser(userId);
if (!changelog.seen) {
// Display the changelog to the user
console.log(`New Update: ${changelog.title}`);
console.log(changelog.content);
// Mark as seen after user acknowledges
await client.changelogs.acknowledge(changelog._id, userId);
console.log('Changelog marked as seen');
} else {
console.log('User has already seen the latest changelog');
}
} catch (error) {
console.error('Error displaying changelog:', error);
}
}
// Usage
await showLatestChangelog('user-123');List All Changelogs with Formatting
Fetch and display all changelogs with proper date formatting:
async function displayAllChangelogs() {
try {
const changelogs = await client.changelogs.list();
changelogs.forEach(changelog => {
const date = new Date(changelog._creationTime);
console.log(`
📢 ${changelog.title}
📅 ${date.toLocaleDateString()}
${changelog.content}
${'─'.repeat(50)}
`);
});
} catch (error) {
console.error('Error fetching changelogs:', error);
}
}
await displayAllChangelogs();User Onboarding Flow
Create a user when they sign up and show them the latest changelog:
async function onboardUser(userId: string, username: string) {
try {
// Create the user in the system
const response = await client.users.create(userId);
console.log(`✓ ${response.message}`);
// Get the latest changelog
const latest = await client.changelogs.getLatest();
if (latest.length > 0) {
console.log(`
Welcome, ${username}! 🎉
Here's what's new:
${latest[0].title}
${latest[0].content}
`);
}
} catch (error) {
console.error('Error during onboarding:', error);
}
}
await onboardUser('user-456', 'Alice');React Component Example
Integrate the SDK into a React component using React Query:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Unchange, type ChangelogWithSeenStatus } from 'unchange-sdk';
const client = new Unchange({
apiKey: process.env.NEXT_PUBLIC_UNCHANGE_API_KEY!,
projectId: process.env.NEXT_PUBLIC_UNCHANGE_PROJECT_ID!,
});
export function ChangelogNotification({ userId }: { userId: string }) {
const queryClient = useQueryClient();
// Fetch latest changelog for user
const { data: changelog, isLoading } = useQuery({
queryKey: ['changelog', 'latest', userId],
queryFn: () => client.changelogs.getLatestForUser(userId),
});
// Acknowledge changelog mutation
const acknowledgeMutation = useMutation({
mutationFn: (changelogId: string) =>
client.changelogs.acknowledge(changelogId, userId),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['changelog', 'latest', userId] });
},
});
const handleAcknowledge = () => {
if (!changelog) return;
acknowledgeMutation.mutate(changelog._id);
};
if (isLoading) return <div>Loading...</div>;
if (!changelog || changelog.seen) return null;
return (
<div className="notification">
<h3>{changelog.title}</h3>
<p>{changelog.content}</p>
<button
onClick={handleAcknowledge}
disabled={acknowledgeMutation.isPending}
>
{acknowledgeMutation.isPending ? 'Marking...' : 'Mark as Read'}
</button>
</div>
);
}Setup React Query Provider
Don't forget to wrap your app with QueryClientProvider:
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}Advanced React Query Patterns
Fetch All Changelogs
import { useQuery } from '@tanstack/react-query';
function ChangelogList() {
const { data: changelogs, isLoading, error } = useQuery({
queryKey: ['changelogs'],
queryFn: () => client.changelogs.list(),
});
if (isLoading) return <div>Loading changelogs...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{changelogs?.map(changelog => (
<article key={changelog._id}>
<h2>{changelog.title}</h2>
<p>{changelog.content}</p>
<time>{new Date(changelog._creationTime).toLocaleDateString()}</time>
</article>
))}
</div>
);
}Create User Mutation
import { useMutation } from '@tanstack/react-query';
function UserOnboarding({ userId }: { userId: string }) {
const createUserMutation = useMutation({
mutationFn: (userId: string) => client.users.create(userId),
onSuccess: (data) => {
console.log(data.message);
},
onError: (error) => {
console.error('Failed to create user:', error);
},
});
return (
<button
onClick={() => createUserMutation.mutate(userId)}
disabled={createUserMutation.isPending}
>
{createUserMutation.isPending ? 'Creating...' : 'Create Account'}
</button>
);
}Custom Hook Pattern
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Custom hook for changelog operations
function useChangelog(userId: string) {
const queryClient = useQueryClient();
const latest = useQuery({
queryKey: ['changelog', 'latest', userId],
queryFn: () => client.changelogs.getLatestForUser(userId),
enabled: !!userId,
});
const acknowledge = useMutation({
mutationFn: (changelogId: string) =>
client.changelogs.acknowledge(changelogId, userId),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['changelog', 'latest', userId]
});
},
});
return {
changelog: latest.data,
isLoading: latest.isLoading,
isError: latest.isError,
acknowledge: acknowledge.mutate,
isAcknowledging: acknowledge.isPending,
};
}
// Usage
function MyComponent({ userId }: { userId: string }) {
const { changelog, isLoading, acknowledge, isAcknowledging } = useChangelog(userId);
if (isLoading) return <div>Loading...</div>;
if (!changelog || changelog.seen) return null;
return (
<div>
<h3>{changelog.title}</h3>
<p>{changelog.content}</p>
<button onClick={() => acknowledge(changelog._id)} disabled={isAcknowledging}>
Mark as Read
</button>
</div>
);
}Next.js API Route Example
Create a server-side API route to fetch changelogs:
// app/api/changelogs/route.ts
import { NextResponse } from 'next/server';
import { Unchange } from 'unchange-sdk';
const client = new Unchange({
apiKey: process.env.UNCHANGE_API_KEY!,
projectId: process.env.UNCHANGE_PROJECT_ID!,
});
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const userId = searchParams.get('userId');
if (userId) {
const changelog = await client.changelogs.getLatestForUser(userId);
return NextResponse.json(changelog);
} else {
const changelogs = await client.changelogs.list();
return NextResponse.json(changelogs);
}
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch changelogs' },
{ status: 500 }
);
}
}
export async function POST(request: Request) {
try {
const body = await request.json();
const { changelogId, userId } = body;
await client.changelogs.acknowledge(changelogId, userId);
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to acknowledge changelog' },
{ status: 500 }
);
}
}Cleanup on User Deletion
Delete a user when they close their account:
async function handleAccountDeletion(userId: string) {
try {
// Delete user and their changelog acknowledgments
await client.users.delete(userId);
console.log(`User ${userId} successfully deleted`);
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
}
await handleAccountDeletion('user-789');Batch Operations
Process multiple users efficiently:
async function acknowledgeForMultipleUsers(
changelogId: string,
userIds: string[]
) {
const results = await Promise.allSettled(
userIds.map(userId =>
client.changelogs.acknowledge(changelogId, userId)
)
);
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`✓ ${successful} users acknowledged`);
if (failed > 0) {
console.log(`✗ ${failed} failed`);
}
}
await acknowledgeForMultipleUsers('changelog-1', [
'user-1', 'user-2', 'user-3'
]);Error Handling Pattern
Robust error handling with retry logic:
async function fetchWithRetry<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
console.log(`Attempt ${i + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw lastError!;
}
// Usage
const changelogs = await fetchWithRetry(() =>
client.changelogs.list()
);