Skip to main content

Getting Started

This guide will help you set up Sagun in your React application.

Installation

First, install the peer dependencies:

npm install react react-dom redux react-redux redux-saga immutable

Then install Sagun:

npm install @iiiristram/sagun

We also recommend installing typed-redux-saga for strongly-typed saga effects:

npm install typed-redux-saga

TypeScript Configuration

Sagun uses TypeScript decorators. Enable them in your tsconfig.json:

{
"compilerOptions": {
"experimentalDecorators": true
}
}

Bootstrap Your Application

Below is an example of typical application initialization based on redux and redux-saga, with highlighted lines that need to be added to configure the framework:

// bootstrap.tsx
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import React from 'react';
import ReactDOM from 'react-dom';
import { call } from 'typed-redux-saga';
import {
ComponentLifecycleService,
OperationService,
asyncOperationsReducer,
Root,
useOperation,
} from '@iiiristram/sagun';

import { App } from './App';

// 1. Create saga middleware
const sagaMiddleware = createSagaMiddleware();

// 2. Create Redux store with operations reducer
const store = applyMiddleware(sagaMiddleware)(createStore)(
combineReducers({
asyncOperations: asyncOperationsReducer,
})
);

// 3. Configure the path to operations in state
useOperation.setPath(state => state.asyncOperations);

// 4. Create the core services
const operationService = new OperationService();
const componentLifecycleService = new ComponentLifecycleService(operationService);

// 5. Run the saga middleware with core services
sagaMiddleware.run(function* () {
yield* call(operationService.run);
yield* call(componentLifecycleService.run);
});

// 6. Render the application
ReactDOM.render(
<Root
operationService={operationService}
componentLifecycleService={componentLifecycleService}
>
<Provider store={store}>
<App />
</Provider>
</Root>,
document.getElementById('app')
);

Create Your First Service

Services contain your business logic. Here's a simple example:

// services/TodoService.ts
import { Service, operation, daemon, DaemonMode } from '@iiiristram/sagun';
import { call } from 'typed-redux-saga';

interface Todo {
id: string;
title: string;
completed: boolean;
}

export class TodoService extends Service {
// Required: unique identifier for the service
toString() {
return 'TodoService';
}

// @operation saves result to Redux store
// @daemon makes method callable via Redux action
@operation
@daemon()
*fetchTodos() {
const response: Response = yield* call(fetch, '/api/todos');
const todos: Todo[] = yield* call([response, response.json]);
return todos;
}

@daemon(DaemonMode.Last) // Cancel previous call if new one starts
*addTodo(title: string) {
const response: Response = yield* call(fetch, '/api/todos', {
method: 'POST',
body: JSON.stringify({ title }),
});
return yield* call([response, response.json]);
}
}

Use Service in Component

// components/TodoList.tsx
import React, { Suspense } from 'react';
import {
useDI,
useService,
useServiceConsumer,
useOperation,
Operation,
getId
} from '@iiiristram/sagun';
import { TodoService } from '../services/TodoService';

// Page component - initializes the service
export function TodoPage() {
const di = useDI();

// Create and register service instance
const service = di.createService(TodoService);
di.registerService(service);

// Initialize service (calls service.run())
const { operationId } = useService(service);

return (
<Suspense fallback={<div>Loading service...</div>}>
<Operation operationId={operationId}>
{() => <TodoList />}
</Operation>
</Suspense>
);
}

// Child component - consumes the service
function TodoList() {
const { service, actions } = useServiceConsumer(TodoService);

// Subscribe to operation result
const operation = useOperation({
operationId: getId(service.fetchTodos),
suspense: true,
});

const todos = operation?.result || [];

return (
<div>
<button onClick={() => actions.fetchTodos()}>
Refresh
</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
);
}

Next Steps

Now that you have Sagun set up, learn more about: