Skip to content
Open
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
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": false
}
22 changes: 11 additions & 11 deletions docs/capabilities/blocks/blocks_payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
You can use the payments template to build your app or add payment functionality to an existing app.

:::note
[Devvit Web](../../capabilities/devvit-web/devvit_web_overview.mdx) is the recommended approach for all interactive experiences. We recommend [migrating your app](../../earn-money/payments/payments_migrate.md) to Devvit Web payments.
[Devvit Web](../../capabilities/devvit-web/devvit_web_overview.mdx) is the recommended approach for all interactive experiences. We recommend [migrating your app](../../earn-money/payments/payments_migrate.mdx) to Devvit Web payments.
:::

To start with a template, select the payments template when you create a new project or run:
Expand Down Expand Up @@ -161,28 +161,28 @@ Errors thrown within the payment handler automatically reject the order. To prov
This example shows how to issue an "extra life" to a user when they purchase the "extra_life" product.

```ts
import { type Context } from '@devvit/public-api';
import { addPaymentHandler } from '@devvit/payments';
import { Devvit, useState } from '@devvit/public-api';
import { type Context } from "@devvit/public-api";
import { addPaymentHandler } from "@devvit/payments";
import { Devvit, useState } from "@devvit/public-api";

Devvit.configure({
redis: true,
redditAPI: true,
});

const GOD_MODE_SKU = 'god_mode';
const GOD_MODE_SKU = "god_mode";

addPaymentHandler({
fulfillOrder: async (order, ctx) => {
if (!order.products.some(({ sku }) => sku === GOD_MODE_SKU)) {
throw new Error('Unable to fulfill order: sku not found');
throw new Error("Unable to fulfill order: sku not found");
}
if (order.status !== 'PAID') {
throw new Error('Becoming a god has a cost (in Reddit Gold)');
if (order.status !== "PAID") {
throw new Error("Becoming a god has a cost (in Reddit Gold)");
}

const redisKey = godModeRedisKey(ctx.postId, ctx.userId);
await ctx.redis.set(redisKey, 'true');
await ctx.redis.set(redisKey, "true");
},
});
```
Expand All @@ -204,14 +204,14 @@ Your app can acknowledge or reject the order. For example, for goods with limite
Use the `useProducts` hook or `getProducts` function to fetch details about products.

```tsx
import { useProducts } from '@devvit/payments';
import { useProducts } from "@devvit/payments";

export function ProductsList(context: Devvit.Context): JSX.Element {
// Only query for products with the metadata "category" of value "powerup".
// The metadata field can be empty - if it is, useProducts will not filter on metadata.
const { products } = useProducts(context, {
metadata: {
category: 'powerup',
category: "powerup",
},
});

Expand Down
70 changes: 40 additions & 30 deletions docs/capabilities/blocks/optimize_performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Use `context.cache` to reduce the amount of requests to optimize performance and

### Leverage scheduled jobs to fetch or update data

Use [scheduler](../server/scheduler.md) to make large data requests in the background and store it in [Redis](../server/redis.mdx) for later use. You can also [fetch data for multiple users](#how-to-cache-data).
Use [scheduler](../server/scheduler.mdx) to make large data requests in the background and store it in [Redis](../server/redis.mdx) for later use. You can also [fetch data for multiple users](#how-to-cache-data).

### Batch API calls to make parallel requests

Expand All @@ -62,7 +62,7 @@ In Devvit, the first render happens on the server side. Parallel fetch requests
In the render function of this interactive post, the app fetches data about the post, the user, the weather, and the leaderboard stats.

```tsx
import { Devvit, useState } from '@devvit/public-api';
import { Devvit, useState } from "@devvit/public-api";

render: (context) => {
const [postInfo] = useState(async () => {
Expand Down Expand Up @@ -103,7 +103,7 @@ The main difference between these two methods is that `useState` blocks render u
This is the best choice for performance because it allows you to render parts of your application while others may still be loading. Here’s how the same example looks for useAsync:

```tsx
import { Devvit, useAsync } from '@devvit/public-api';
import { Devvit, useAsync } from "@devvit/public-api";

const { data: postInfo, loading: postInfoLoading } = useAsync(async () => {
return await getThreadInfo(context);
Expand All @@ -117,17 +117,19 @@ const { data: weather, loading: weatherLoading } = useAsync(async () => {
return await getTheWeather(context);
});

const { data: leaderboardStats, loading: leaderboardStatsLoading } = useAsync(async () => {
return await getLeaderboard(context);
});
const { data: leaderboardStats, loading: leaderboardStatsLoading } = useAsync(
async () => {
return await getLeaderboard(context);
},
);
```

#### useState

This is the same example using useState.

```tsx
import { Devvit, useState } from '@devvit/public-api';
import { Devvit, useState } from "@devvit/public-api";

render: (context) => {
const [appState, setAppState] = useState(async () => {
Expand Down Expand Up @@ -162,11 +164,11 @@ If you need to update one of the state props, you’ll need to do `setAppState({
The following example shows how unoptimized code for fetching data from an external resource, like a weather API, looks:

```tsx
import { Devvit, useState } from '@devvit/public-api';
import { Devvit, useState } from "@devvit/public-api";

// naive, non-optimal way of fetching that kind of data
const [externalData] = useState(async () => {
const response = await fetch('https://external.weather.com');
const response = await fetch("https://external.weather.com");

return await response.json();
});
Expand All @@ -183,19 +185,19 @@ You can use a cache helper to make one request for data, save the response, and
**Example: fetch weather data every 2 hours with cache helper**

```tsx
import { Devvit, useState } from '@devvit/public-api';
import { Devvit, useState } from "@devvit/public-api";

// optimized, performant way of fetching that kind of data
const [externalData] = useState(async () => {
return context.cache(
async () => {
const response = await fetch('https://external.weather.com');
const response = await fetch("https://external.weather.com");
return await response.json();
},
{
key: `weather_data`,
ttl: 2 * 60 * 60 * 1000, // 2 hours in milliseconds
}
},
);
});
```
Expand All @@ -206,35 +208,35 @@ Do not cache sensitive information. Cache helper randomly selects one user to ma

### Solution: schedule a job

Alternatively, you can use [scheduler](../server/scheduler.md) to make the request in background, save the response to [Redis](../server/redis.mdx), and avoid unnecessary requests to the external resource.
Alternatively, you can use [scheduler](../server/scheduler.mdx) to make the request in background, save the response to [Redis](../server/redis.mdx), and avoid unnecessary requests to the external resource.

**Example: fetch weather data every 2 hours with a scheduled job**

```tsx
import { Devvit } from '@devvit/public-api';
import { Devvit } from "@devvit/public-api";

Devvit.addSchedulerJob({
name: 'fetch_weather_data',
name: "fetch_weather_data",
onRun: async (_event, context) => {
const response = await fetch('https://external.weather.com');
const response = await fetch("https://external.weather.com");
const responseData = await response.json();
await context.redis.set('weather_data', JSON.stringify(responseData));
await context.redis.set("weather_data", JSON.stringify(responseData));
},
});

Devvit.addTrigger({
event: 'AppInstall',
event: "AppInstall",
onEvent: async (_event, context) => {
await context.scheduler.runJob({
cron: '0 */2 * * *', // runs at the top of every second hour
name: 'fetch_weather_data',
cron: "0 */2 * * *", // runs at the top of every second hour
name: "fetch_weather_data",
});
},
});

// inside the render method
const [externalData] = useState(async () => {
return context.redis.get('fetch_weather_data');
return context.redis.get("fetch_weather_data");
});

export default Devvit;
Expand All @@ -254,9 +256,9 @@ Before using realtime, the leaderboard fetching code looked like this:

```tsx
const getLeaderboard = async () =>
await context.redis.zRange('leaderboard', 0, 5, {
await context.redis.zRange("leaderboard", 0, 5, {
reverse: true,
by: 'rank',
by: "rank",
});

const [leaderboard, setLeaderboard] = useState(async () => {
Expand All @@ -274,7 +276,7 @@ leaderboardInterval.start();
And code for updating the leaderboard looked like this:

```tsx
await context.redis.zAdd('leaderboard', { member: username, score: gameScore });
await context.redis.zAdd("leaderboard", { member: username, score: gameScore });
```

### With realtime​
Expand All @@ -285,9 +287,12 @@ This is the updated game completion code:

```tsx
// stays as is
await context.redis.zAdd('leaderboard', { member: username, score: gameScore });
await context.redis.zAdd("leaderboard", { member: username, score: gameScore });
// new code
context.realtime.send('leaderboard_updates', { member: username, score: gameScore });
context.realtime.send("leaderboard_updates", {
member: username,
score: gameScore,
});
```

Now replace the interval with the realtime subscription:
Expand All @@ -298,7 +303,7 @@ const [leaderboard, setLeaderboard] = useState(async () => {
}); // stays as is

const channel = useChannel({
name: 'leaderboard_updates',
name: "leaderboard_updates",
onMessage: (newLeaderboardEntry) => {
const newLeaderboard = [...leaderboard, newLeaderboardEntry] // append new entry
.sort((a, b) => b.score - a.score) // sort by score
Expand Down Expand Up @@ -342,9 +347,12 @@ To do this, you can add:
```tsx
const [subscriberCount] = useState<number>(async () => {
const startSubscribersRequest = Date.now(); // a reference point for the request start
const devvitSubredditInfo = await context.reddit.getSubredditInfoByName('devvit');
const devvitSubredditInfo =
await context.reddit.getSubredditInfoByName("devvit");

console.log(`subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`);
console.log(
`subscribers request took: ${Date.now() - startSubscribersRequest} milliseconds`,
);

return devvitSubredditInfo.subscribersCount || 0;
});
Expand All @@ -359,7 +367,9 @@ const [performanceStartRender] = useState(Date.now()); // a reference point for
Add a console.log before the return statement:

```tsx
console.log(`Getting the data took: ${Date.now() - performanceStartRender} milliseconds`);
console.log(
`Getting the data took: ${Date.now() - performanceStartRender} milliseconds`,
);
```

All of that put together will look like this:
Expand Down
Loading