Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a blog #63

Merged
merged 1 commit into from
Dec 2, 2024
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
17 changes: 14 additions & 3 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export default defineConfig({
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Home", link: "/" },
{ text: "Getting Started", link: "/gettingStarted" },
{ text: "Guide", link: "/query" },
{ text: "Examples", link: "/examples" }
{ text: "Examples", link: "/examples" },
{ text: "Blog", link: "/blog" }
],
sidebar: [
{
Expand Down Expand Up @@ -55,7 +55,18 @@ export default defineConfig({
{
text: "Examples",
items: [
{ text: "Examples", link: "/examples" },
{ text: "All Examples", link: "/examples" },
{ text: "Bears JS", link: "https://codesandbox.io/p/sandbox/leo-query-bears-demo-js-wmwlgt?file=%2Fsrc%2FApp.jsx" },
{ text: "Bears TS", link: "https://codesandbox.io/p/sandbox/leo-query-bears-demo-ts-7f2c34?file=%2Fsrc%2FApp.tsx" },
{ text: "Task Manager", link: "https://xsh8c4.csb.app/" },
]
},
{
text: "Blog",
items: [
{ text: "Recent Posts", link: "/blog" },
{ text: "Implementing Retry Logic", link: "/blog/implementingRetryLogic" },
{ text: "Delaying Execution with Wait", link: "/blog/delayingExecutionWithWait" },
]
}
],
Expand Down
21 changes: 21 additions & 0 deletions docs/blog/delayingExecutionWithWait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Delaying Execution with Wait

Sometimes I want to delay execution in Leo Query. This may be to delay a retry or wait for a React render. I prefer the async / await syntax over `setTimeout`. So I wrote a small `wait` utility function to use in the Leo Query code.

```typescript
const wait = async (timeout?: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, timeout);
});
};

const myFunction = async () => {
//Do something
await wait(5 * 1000); //Wait 5 seconds
//Do more things
};
```

Happy Coding!
77 changes: 77 additions & 0 deletions docs/blog/implementingRetryLogic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Implementing Retry Logic

Lots of things can go wrong when you're making HTTP requests. Wifi can cut out. Servers can overload. Apps need to have a retry strategy for good UX. Leo Query implements a nuanced retry strategy.

When building a retry strategy you need to answer three questions.

### When should you start retrying?

You should retry as soon as possible. Network or server errors often resolve quickly.

```typescript
const trigger = () => {
const promise = fetch();
promise.catch(() => retry(p.fn, q, promise)); //If the promise fails immediately retry
};
```

### How long should you wait between each retry?

Three common strategies for waiting are constant backoff, linear backoff, and exponential backoff.

Constant backoff waits the same amount of time between retries. This strategy is good for keeping your system simple and retrying quickly.

Linear backoff waits a linearly increasing amount between each retry. Linear backoffs are good for still keeping your system simple but giving the issue more time to resolve.

Exponential backoff waits exponentially more time between each retry. Exponential backoffs work well because they retry quickly initially and then give issue more time to resolve in later retries. This pattern works well because most issues resolve quickly, but issues that do not tend to take significantly longer. Leo Query uses a modified exponential backoff.

```typescript
const calculateBackoffDelay = (attempt: number) =>
attempt === 0 ? 0 : Math.min((2 ** (attempt - 1)) * 1000, 30 * 1000);
```

### When should you stop retrying?

You should stop retrying when you no longer need the data or when there's no hope left to retrieve the data. Leo Query keeps track of when the data is no longer needed. And by default it gives up after 5 retries.

## Implementation

Putting it all together this is what the [retry](https://github.com/steaks/leo-query/blob/main/src/retry.ts) code looks like.

```typescript
const wait = async (timeout?: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, timeout);
});
};

export const calculateBackoffDelay = (attempt: number) =>
attempt === 0 ? 0 : Math.min((2 ** (attempt - 1)) * 1000, 30 * 1000);

export const retry = async <State, R>(fn: () => Promise<R>, query: Query<State, R>, promise: Promise<R>, attempt: number = 0): Promise<R> => {
try {
return await fn();
} catch (error) {
if (attempt >= query.__retries) { //Defaults to 5 retries
throw error;
}
const state = query.__store().getState();
const current = state[query.__key] as Query<State, R>;
if (current.__trigger !== promise) { //If the query has been re-triggered then we no longer need the data
throw error;
}
const backoffDelay = calculateBackoffDelay(attempt); //Use exponential backoff
await wait(backoffDelay);
return retry(fn, query, promise, attempt + 1);
}
};

const trigger = () => {
const promise = fetch();
promise.catch(() => retry(p.fn, q, promise)); //If the promise fails then we immediately retry
};
```

Happy Coding!
54 changes: 54 additions & 0 deletions docs/blog/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<style>
.blog-post {
border: 1px solid #efefef;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
}
.blog-post .post-meta {
color: #666;
margin: 0;
}
.blog-post h1 {
font-size: 2.5em;
margin-bottom: 0px;
}
.blog-post h3 {
margin: 0;
}
.blog-post p:last-child {
margin-bottom: 0;
}
.blog-post .read-more {
color: #666;
font-style: italic;
text-decoration: none;
}
.blog-post .read-more:hover {
text-decoration: underline;
}
</style>

# Blog

Welcome to the Leo Query blog! Here you'll find articles about state management, data fetching, and more.

## Latest Posts

<div class="blog-post">
<h3><a href="/blog/implementingRetryLogic">Implementing Retry Logic</a></h3>
<p class="post-meta">By Steven Wexler • Dec 1, 2024</p>
<p>
Lots of things can go wrong when you're making HTTP requests. Wifi can cut out. Servers can overload. Apps need to have a retry strategy for good UX. Leo Query implements a nuanced retry strategy.....
<a href="/blog/implementingRetryLogic" class="read-more">read more</a>
</p>
</div>

<div class="blog-post">
<h3><a href="/blog/delayingExecutionWithWait">Delaying Execution with Wait</a></h3>
<p class="post-meta">By Steven Wexler • Dec 1, 2024</p>
<p>
Sometimes I want to delay execution in Leo Query. This may be to delay a retry or wait for a React render. I prefer the async / await syntax over `setTimeout`. So I wrote a small `wait` utility function to use in the Leo Query code.....
<a href="/blog/delayingExecutionWithWait" class="read-more">read more</a>
</p>
</div>
2 changes: 1 addition & 1 deletion docs/examples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Examples

Here are some live examples to see leo-query.
Here are live examples to see Leo Query in action.

| Example | Description |
|---------------------------------------------------------------------------------------------------|------------------------------------|
Expand Down