You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
iAPI: Introduce AsyncAction and TypeYield type helpers (#70422)
* Introduce AsyncAction and TypeYield helpers
* Use the helpers on the other tests
* Update docs for AsyncAction
* Update docs for TypeYield
* Export the helpers
* Replace manual typing with satisfies on TypeYield return
* Improve docs
* Update changelog
---------
Co-authored-by: luisherranz <[email protected]>
Co-authored-by: DAreRodz <[email protected]>
Copy file name to clipboardExpand all lines: docs/reference-guides/interactivity-api/core-concepts/using-typescript.md
+54-9Lines changed: 54 additions & 9 deletions
Original file line number
Diff line number
Diff line change
@@ -471,39 +471,84 @@ type Store = {
471
471
};
472
472
```
473
473
474
-
There's something to keep in mind when when using asynchronous actions. Just like with the derived state, if the asynchronous action needs to return a value and this value directly depends on some part of the global state, TypeScript will not be able to infer the type due to a circular reference.
474
+
There's something to keep in mind when using asynchronous actions. Just like with the derived state, if an asynchronous action uses `state` within a `yield` expression (for example, by passing `state` to an async function that is then yielded) or if its return value depends on `state`, TypeScript might not be able to infer the types correctly due to a potential circular reference.
yield new Promise( ( r ) => setTimeout( r, 1000 ) );
484
-
return state.counter; // TypeScript can't infer this return type.
482
+
*delayedOperation() {
483
+
// Example: state.counter is used as part of the yielded logic.
484
+
yield fetchCounterData( state.counter );
485
+
486
+
// And/or the final return value depends on state.
487
+
return state.counter + 1;
485
488
},
486
489
},
487
490
} );
488
491
```
489
492
490
-
In this case, just as we did with the derived state, we must manually type the return value of the generator.
493
+
In such cases, TypeScript might issue a warning about a circular reference or default to `any`. To solve this, you need to manually type the generator function. The Interactivity API provides a helper type, `AsyncAction<ReturnType>`, for this purpose.
491
494
492
495
```ts
496
+
import { store, type AsyncAction } from '@wordpress/interactivity';
yield new Promise( ( r ) => setTimeout( r, 1000 ) );
500
-
return state.counter; // Now this is correctly inferred.
503
+
*delayedOperation(): AsyncAction< number > {
504
+
// Now, this doesn't cause a circular reference.
505
+
yield fetchCounterData( state.counter );
506
+
507
+
// Now, this is correctly typed.
508
+
return state.counter + 1;
509
+
},
510
+
},
511
+
} );
512
+
```
513
+
514
+
That's it! The `AsyncAction<ReturnType>` helper is defined as `Generator<any, ReturnType, unknown>`. By using `any` for the type of values yielded by the generator, it helps break the circular reference, allowing TypeScript to correctly infer the types when `state` is involved in `yield` expressions or in the final return value. You only need to specify the final `ReturnType` of your asynchronous action.
515
+
516
+
### Typing yielded values in asynchronous actions
517
+
518
+
While `AsyncAction<ReturnType>` types the overall generator and its final return value, the value resolved by an individual `yield` expression within that generator might still be typed as `any`.
519
+
520
+
If you need to ensure the correct type for a value that a `yield` expression resolves to (e.g., the result of a `fetch` call or another async operation), you can use the `TypeYield<T>` helper. This helper takes the type of the asynchronous function/operation being yielded (`T`) and resolves to the type of the value that the promise fulfills with.
521
+
522
+
Suppose `fetchCounterData` returns a promise that resolves to an object:
523
+
524
+
```ts
525
+
import { store, type AsyncAction, type TypeYield } from '@wordpress/interactivity';
526
+
527
+
// Assume this function is defined elsewhere and fetches specific data.
528
+
const fetchCounterData = async ( counterValue: number ): Promise< { current: number, next: number } > => {
// Use TypeYield to correctly type the resolved value of the yield.
539
+
const data = ( yield fetchCounterData( state.counter ) ) as TypeYield< typeof fetchCounterData >;
540
+
541
+
// Now, `data` is correctly typed as { current: number, next: number }.
542
+
console.log( data.current, data.next );
543
+
544
+
// Update state based on the fetched data.
545
+
state.counter = data.next;
501
546
},
502
547
},
503
548
} );
504
549
```
505
550
506
-
That's it! Remember that the return type of a Generator is the second generic argument: `Generator< unknown, ReturnType, unknown >`.
551
+
In this example, `( yield fetchCounterData( state.counter ) ) as TypeYield< typeof fetchCounterData >` ensures that the `data` constant is correctly typed as `{ current: number, next: number }`, matching the return type of `fetchCounterData`. This allows you to confidently access properties like `data.current` and `data.next` with type safety.
507
552
508
553
## Typing stores that are divided into multiple parts
Copy file name to clipboardExpand all lines: packages/interactivity-router/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,7 @@
6
6
7
7
- Export `NavigateOptions` and `PrefetchOptions` types. ([#70315](https://github.com/WordPress/gutenberg/pull/70315))
8
8
- Support new styles and script modules on client-side navigation, including a new full-page client-side navigation mode. ([#70353](https://github.com/WordPress/gutenberg/pull/70353))
9
+
- Introduce `AsyncAction` and `TypeYield` type helpers. ([#70422](https://github.com/WordPress/gutenberg/pull/70422))
0 commit comments