- Creates actions for service methods marked with the @daemon decorator.
- Optionally binds these actions to a provided store.
```
--------------------------------
### useService Hook as Shortcut
Source: https://github.com/iiiristram/sagun/blob/master/README.md
A shortcut for useSaga, simplifying the binding of service methods (run, dispose) to the component lifecycle. It takes a service instance and arguments, returning an operationId for tracking.
```tsx
const { operationId } = useService(service, [...args]);
```
```tsx
const { operationId } = useSaga(
{
onLoad: service.run,
onDispose: service.dispose,
},
[...args]
);
```
--------------------------------
### useOperation Hook for Suspense-Compatible Operations
Source: https://github.com/iiiristram/sagun/blob/master/README.md
Creates a subscription to an operation in the Redux store, compatible with React.Suspense. It allows falling back to a loader while an operation executes and requires setting a path in the store for operation lookup.
```ts
import { Service, operation } from '@iiiristram/sagun';
class MyService extends Service {
toString() {
return 'MyService';
}
@operation
*foo() {
return 'Hello';
}
}
```
```tsx
import { useServiceConsumer, useOperation, getId } from '@iiiristram/sagun';
function MyComponent() {
const { service } = useServiceConsumer(MyService);
const operation = useOperation({
operationId: getId(service.foo),
suspense: true, // turn on Suspense compatibility
});
return {operation?.result} World
;
}
```
```tsx
import { Suspense } from 'react';
function Parent() {
return (
);
}
```
```ts
// bootstrap.ts
useOperation.setPath(state => ...)
// i.e. state => state.asyncOperations
```
--------------------------------
### SSR with Sagas and @operation Decorator
Source: https://github.com/iiiristram/sagun/blob/master/README.md
Explains how to enable Server-Side Rendering (SSR) for sagas using the @operation decorator with the 'ssr: true' option. It details collecting pure data on the server and rendering components that await operation results via Suspense and the Operation component.
```typescript
// MyService.ts
class MyService extends Service {
@operation({
// Enable ssr for operation, so its result will be collected.
// Operations marked this way won't be executed on client at first time,
// so don't put here any logic with application state, like forms,
// such logic probably has to be also executed on the client.
// You should collect pure data here.
ssr: true
})
*fetchSomething() {
// ...
}
}
// MyComponent.tsx
function MyComponent() {
const {operationId} = useSaga({
onLoad: myService.fetchSomething,
})
return (
// subscribe to saga that contains the operation via Operation or useOperation,
// if no subscription, render won't await this saga
{({result}) => }
)
}
// App.tsx
function App() {
return (
// ensure there is Suspense that will handle your operation
)
}
```
--------------------------------
### AsyncOperation and OperationId Types
Source: https://github.com/iiiristram/sagun/blob/master/README.md
Defines the core data structure `AsyncOperation` for managing asynchronous states and the custom `OperationId` type for uniquely identifying operations within the application state.
```APIDOC
AsyncOperation
Represents the state of an asynchronous operation.
Properties:
id: OperationId - Unique identifier for the operation.
isLoading?: boolean - True if the operation is currently in progress.
isError?: boolean - True if the operation finished with an error.
isBlocked?: boolean - True if the operation should not be executed.
error?: TErr - The error object if the operation failed.
args?: TArgs - The arguments the operation was executed with.
result?: TRes - The successful result of the operation.
meta?: TMeta - Any additional metadata associated with the operation.
OperationId
A custom type that extends `string` to provide type-safe identification of operations.
At runtime, it is a string, but it enforces type checking at compile time.
Example:
const id = 'MY_ID' as OperationId;
const str: string = id; // OK, type information is lost at runtime
// operation.id = str; // TYPE ERROR: id must be of type OperationId
```
--------------------------------
### Sagun @daemon Decorator
Source: https://github.com/iiiristram/sagun/blob/master/README.md
The @daemon decorator provides metadata for service methods, allowing them to be invoked by Redux actions after `service.run` is called. It controls execution modes like synchronous, latest, every, and scheduling.
```ts
import {Service, daemon, DaemonMode} from '@iiiristram/sagun';
class MyService extends Service {
toString() {
return 'MyService'
}
// by default method won't be called until previous call finished (DaemonMode.Sync).
// i.e. block new page load until previous page loaded
@daemon()
*method_1(a: number, b: number) {
...
}
// cancel previous call and starts new (like redux-saga takeLatest)
// i.e. multiple clicks to "Search" button
@daemon(DaemonMode.Last)
*method_2(a: number, b: number) {
...
}
// call method every time, no order guarantied (like redux-saga takeEvery)
// i.e. send some analytics
@daemon(DaemonMode.Every)
*method_3(a: number, b: number) {
...
}
// has no corresponding action, after service run, method will be called every N ms, provided by second argument
// i.e. make some polling
@daemon(DaemonMode.Schedule, ms)
*method_4() {
...
}
@daemon(
// DaemonMode.Sync / DaemonMode.Last / DaemonMode.Every
mode,
// provide action to trigger instead of auto-generated action, same type as redux-saga "take" effect accepts
action
)
*method_5(a: number, b: number) {
...
}
}
```
--------------------------------
### Sagun @inject Decorator
Source: https://github.com/iiiristram/sagun/blob/master/README.md
The @inject decorator is applied to service constructor arguments to enable dependency resolution. It allows services to declare their dependencies, such as other services.
```ts
// MyService.ts
import {Service, inject} from '@iiiristram/sagun';
class MyService extends Service {
...
constructor(
// default dependency for all services
@inject(OperationService) operationService: OperationService,
@inject(MyOtherService) myOtherService: MyOtherService
) {
super(operationService)
...
}
...
}
```
--------------------------------
### useSaga Hook for Component Lifecycle
Source: https://github.com/iiiristram/sagun/blob/master/README.md
Binds saga execution to the component lifecycle, executing on render for Suspense compatibility. It's used for application logic like form initialization or aggregating service methods. Handles cancellation of long-running tasks and ensures proper disposal.
```tsx
function MyComponent(props) {
const {a, b} = props;
// operationId to subscribe to onLoad results
const {operationId} = useSaga({
// "id" required in case component placed inside Suspense boundary,
// cause in some cases React drop component state,
// and it's impossible to generate stable implicit id.
// See https://github.com/facebook/react/issues/24669.
//
// Id has to be uniq for each component instance (i.e. use `item_${id}` for list items).
id: "operation-id",
// executes on render and args changed
onLoad: function*(arg_a, arg_b) {
console.log('I am rendered')
yield call(service1.foo, arg_a)
yield call(service2.bazz, arg_b)
},
// executes before next onLoad and on unmount
onDispose: function*(arg_a, arg_b) {
console.log('I was changed')
}
// arguments for sagas, so sagas re-executed on any argument change
}, [a, b])
...
}
```
--------------------------------
### Save Service Method Results with @operation
Source: https://github.com/iiiristram/sagun/blob/master/README.md
Explains how to use the `@operation` decorator to automatically save the return value of a service method to a Redux store. It also shows how to consume these results in a React component using `useServiceConsumer` and `useOperation` by referencing the operation ID derived from the service method.
```typescript
// MyService.ts
import { Service, operation } from '@iiiristram/sagun';
class MyService extends Service {
toString() {
return 'MyService';
}
@operation
*foo() {
return 'Hello';
}
}
```
```tsx
// MyComponent.tsx
import { useServiceConsumer, useOperation, getId } from '@iiiristram/sagun';
function MyComponent() {
// resolve service instance, that was registered somewhere in parent components
const { service } = useServiceConsumer(MyService);
const operation = useOperation({
operationId: getId(service.foo), // get operation id from service method
});
return {operation?.result} World
;
}
```
--------------------------------
### Sagun @operation Decorator
Source: https://github.com/iiiristram/sagun/blob/master/README.md
The @operation decorator creates an operation in the Redux store for a wrapped service method. It supports auto-generated IDs, custom IDs, function-based IDs, and configuration options like update strategies and SSR enablement.
```ts
import {Service, operation, OperationId} from '@iiiristram/sagun';
export const MY_CUSTOM_ID = 'MY_CUSTOM_ID' as OperationId
class MyService extends Service {
toString() {
return 'MyService'
}
// create an operation with auto-generated id,
// which can be retrieved by util "getId"
@operation
*method_1() {
...
}
// create an operation with provided id, i.e. it's possible to assign same operation for different methods
@operation(MY_CUSTOM_ID)
*method_2() {
return 1;
}
// create an operation id depending on arguments provided for method
@operation((...args) => args.join('_') as OperationId)
*method_3(...args) {
return 1;
}
@operation({
// optional, could be constant or function
id,
// optional, function that allows to change operation values, but it should not change operation generics
// (ie if operation result was a number it should be a number after change)
updateStrategy: function*(operation) {
const changedOperation = ... // change operation somehow
return changedOperation
},
ssr: true, // enable execution on server
})
*method_4(...args) {
return 1;
}
}
// Update strategy example
import { Service, operation, OperationId } from '@iiiristram/sagun';
class MyServiceWithUpdate extends Service {
toString() {
return 'MyServiceWithUpdate';
}
@operation({
updateStrategy: function* mergeStrategy(next) {
const prev = yield select(state => state.asyncOperations.get(next.id));
return {
...next,
result: prev?.result && next.result ? [...prev.result, ...next.result] : next.result || prev?.result,
};
},
})
*loadList(pageNumber: number) {
const items: Array = yield call(fetch, { pageNumber });
return items;
}
}
```
=== COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.