Skip to content

Commit

Permalink
update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Kim committed Mar 4, 2024
1 parent a95d5fc commit 9983390
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 26 deletions.
92 changes: 71 additions & 21 deletions website/documents/guides/07-lifecycles.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,60 @@ function *Component() {
renderer.render(<Component />, document.body);
```

## Cleanup logic
While you can use context iterators to write cleanup logic after `for...of` and `for await...of` loops, this does not handle errors, and this cleanup logic cannot be written outside of component functions. To solve the first issue, you can use `try`/`finally`. When a generator component is removed from the tree, Crank calls the `return` method on the component’s generator object. You can think of it as whatever `yield` expression your component was suspended on being replaced by a `return` statement. This means any loops your component was in when the generator suspended are broken out of, and code after the yield does not execute.

You can take advantage of this behavior by wrapping your `yield` loops in a `try`/`finally` block to release any resources that your component may have used.

```jsx
import {renderer} from "@b9g/crank/dom";

function *Cleanup() {
try {
while (true) {
yield "Hi";
}
} finally {
console.log("finally block executed");
}
}

renderer.render(<Cleanup />, document.body);
console.log(document.body); // "Hi"
renderer.render(null, document.body);
// "finally block executed"
console.log(document.body); // ""
```

[The same best practices](https://eslint.org/docs/rules/no-unsafe-finally) which apply to `try`/`finally` statements in regular functions apply to generator components. In short, you should not yield or return anything in the `finally` block. Crank will not use the yielded or returned values and doing so might cause your components to inadvertently swallow errors or suspend in unexpected locations.

To write cleanup logic which can be abstractd outside the component function, you can use the `cleanup()` method on the context. This method is similar to `flush() and `schedule()` in that it takes a callback.


```jsx live
import {renderer} from "@b9g/crank/dom";
function addGlobalEventListener(ctx, type, listener, options) {
window.addEventListener(type, listener, options);
// ctx.cleanup allows you to write cleanup logic outside the component
ctx.cleanup(() => window.removeEventListener(type, listener, options));
}

function *KeyboardListener() {
let key = "";
const listener = (ev) => {
key = ev.key;
this.refresh();
};

addGlobalEventListener(this, "keypress", listener);
for ({} of this) {
yield <div>Last key pressed: {key || "N/A"}</div>
}
}

renderer.render(<KeyboardListener />, document.body);
```

## Catching Errors

It can be useful to catch errors thrown by components to show the user an error notification or to notify error-logging services. To facilitate this, Crank will cause `yield` expressions to rethrow errors which happen when rendering children. You can take advantage of this behavior by wrapping your `yield` operations in a `try`/`catch` block to catch errors caused by children.
Expand Down Expand Up @@ -202,32 +256,28 @@ function *Catcher() {
renderer.render(<Catcher />, document.body);
```

## Additional cleanup methods

When a generator component is removed from the tree, Crank calls the `return` method on the component’s generator object. You can think of it as whatever `yield` expression your component was suspended on being replaced by a `return` statement. This means any loops your component was in when the generator suspended are broken out of, and code after the yield does not execute.
## Returning values from generator components

You can take advantage of this behavior by wrapping your `yield` loops in a `try`/`finally` block to release any resources that your component may have used.
When you return from a generator component, the returned value is rendered and the component scope is thrown away, same as would happen when using a function component. This means that while the component cannot have local variables, but represent sequences of renderings.

```jsx
```jsx live
import {renderer} from "@b9g/crank/dom";
function *Component() {
yield <div>1</div>;
yield <div>2</div>;
return <div>3</div>;
}

function *Cleanup() {
try {
while (true) {
yield "Hi";
}
} finally {
console.log("finally block executed");
function *App() {
for ({} of this) {
yield (
<div>
<Component />
<button onclick={() => this.refresh()}>Refresh</button>
</div>
);
}
}

renderer.render(<Cleanup />, document.body);
console.log(document.body); // "Hi"
renderer.render(null, document.body);
// "finally block executed"
console.log(document.body); // ""
renderer.render(<App />, document.body);
```

[The same best practices](https://eslint.org/docs/rules/no-unsafe-finally) which apply to `try`/`finally` statements in regular functions apply to generator components. In short, you should not yield or return anything in the `finally` block. Crank will not use the yielded or returned values and doing so might cause your components to inadvertently swallow errors or suspend in unexpected locations.

## Returning Values
11 changes: 7 additions & 4 deletions website/documents/guides/08-reusable-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ Anything can be passed as a key to the `provide` and `consume` methods, so you c

**Note:** Crank does not link “providers” and “consumers” in any way, and doesn’t automatically refresh consumer components when the `provide` method is called. It’s up to you to ensure consumers update when providers update.

### context.schedule
You can pass a callback to the `schedule` method to listen for when the component renders. Callbacks passed to `schedule` fire synchronously after the component commits, with the rendered value of the component as its only argument. Scheduled callbacks fire once per call and callback function (think `requestAnimationFrame`, not `setInterval`). This means you have to continuously call the `schedule` method for each update if you want to execute some code every time your component commits.
### `context.schedule()`
You can pass a callback to the `schedule()` method to listen for when the component renders. Callbacks passed to `schedule` fire synchronously after the component renders, with the rendered value of the component as its only argument. Scheduled callbacks fire once per call and callback function per update (think `requestAnimationFrame()`, not `setInterval()`). This means you have to continuously call the `schedule` method for each update if you want to execute some code every time your component commits.

### context.cleanup
### `context.flush()`
Similar to the `schedule()` method, this method fires when rendering has finished. Unlike the `schedule()` method, this method fires when a component’s rendered DOM is live. This is useful when you need to do something like calling `focus()` on an `<input>` element or perform DOM measurement calculations. Callbacks fire once per call and callback function per update.

### `context.cleanup()`
Similarly, you can pass a callback to the `cleanup` method to listen for when the component unmounts. Callbacks passed to `cleanup` fire synchronously when the component is unmounted. Each registered callback fires only once. The callback is called with the last rendered value of the component as its only argument.

## Strategies for Reusing Logic
Expand All @@ -73,7 +76,7 @@ import {Context} from "@bikeshaving/crank";

const ContextIntervalSymbol = Symbol.for("ContextIntervalSymbol");

Context.prototype.setInterval = function(callback, delay, ...args) {
Context.prototype.setInterval = function(callback, delay, ...args) {`
const interval = window.setInterval(callback, delay, ...args);
if (typeof this[ContextIntervalSymbol] === "undefined") {
this[ContextIntervalSymbol] = new Set();
Expand Down
1 change: 0 additions & 1 deletion website/src/views/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export default async function Guide({
<${Sidebar} docs=${docs} url=${url} title="Guides" />
<${Main}>
<h1>${title}</h1>
<marquee behavior="alternate">🚧 The docs are a work in progress.🚧</marquee>
<${Marked} markdown=${body} components=${components} />
<//Main>
<//Root>
Expand Down

0 comments on commit 9983390

Please sign in to comment.