Sitka is a small but powerful framework for state management. It organizes your application into modules, each managing a logical area of state. It builds on top of Redux and Redux Saga, giving you all the benefits of those tools without the boilerplate.

Lets examine a simple counter application, contrasting the differences between a traditional Redux / Redux Saga implementation and a Sitka implementation.

A simple counter application

For the purposes of illustration, we'll work with a simple counter app built using TypeScript, whose state exists in a Redux store, and uses Redux Saga to coordinate its logic.

In simple applications, it may be enough to change state by calling an action directly consumed by a Redux reducer. However, in larger apps, Sagas offer a useful mechanism for triggering compound operations around a single logical action such as "increment counter". For example, the increment action might require permissions checks, logging, and even asynchronous access to a secondary data store before finally calling a reducer to change state.

The counter app discussed here is a simplified model of a much larger application, similar to others we’ve created at Olio Apps.

The traditional way

Building the counter app using TypeScript, Redux, and Redux Saga will require:

the counter state in Redux an interface for the increment action creator an action creator that uses the interface a listener for this action in the Sagas index, routing to a Saga a selector to get the current counter state from Redux a Saga, which handles the action's payload as well as any other application side-effects, and setting a new value in Redux state via reducer, using a second Redux action creator an interface for this reducer-facing action creator an action for the reducer-facing action creator a reducer listening for the value-setting action creator registration of the reducer with the root reducer

Below we have a fully realized implementation of these components:

interface Counter { value : number } const defaultCounterState : Counter = { value : 0 } interface AppState { counter : Counter } const defaultAppState : AppState = { counter : defaultCounterState , } interface HandleIncrementAction { type : "HANDLE_INCREMENT" , } const handleIncrement = ( ) : HandleIncrementAction => ( { type : "HANDLE_INCREMENT" } ) default function * root ( ) : { } { yield [ takeEvery ( "HANDLE_INCREMENT" , handleIncrement ) ] } function selectCounter ( state : AppState ) : Counter { return state . counter } function * handleIncrement ( action : HandleIncrementAction ) : { } { const counter = yield select ( selectCounter ) const newValue = counter . value + 1 yield put ( actions . setCounter ( newCounter ) ) } interface SetCounter { type : "SET_COUNTER" value : number } const setCounter = ( value : number ) => ( { type : "SET_COUNTER" , value , } ) function counter ( state : Counter = defaultCounterState , action : SetCounterAction , ) : number { switch ( action . type ) { case "SET_COUNTER" : return { ... state , value : action . value } default : return state } } const rootReducer = redux . combineReducers ( { counter , } ) const store = createStoreWithMiddleware ( rootReducer ) sagaMiddleware . run ( root ( ) )

TypeScript, Redux and Redux-Saga are great to use together. You gain all the advantages of a strongly-typed codebase, Redux state management, and straightforward control flow of both synchronous and asynchronous operations.

But as you can see from above, an obvious downside of this stack is that typical usage requires a lot of boilerplate! That is where Sitka comes in handy.

Implementing the app using Sitka

Sitka dramatically cuts down the amount of boilerplate. All the code that is needed to accomplish the same counter application above can be written using Sitka like this:

interface CounterState { readonly value : number } interface AppModules { counter : CounterModule } class CounterModule extends SitkaModule < CounterState , AppModules > { public moduleName : string = "counter" public defaultState : CounterState = { value : 0 , } public * handleIncrement ( ) : { } { const counter : CounterState = yield select ( this . getCounter ) const newValue = counter . value + 1 yield put ( this . setState ( { value : newValue } ) ) } private getCounter ( state : AppState ) : CounterState { return state . counter } } const sitka = new Sitka < AppModules > ( ) sitka . register ( [ new CounterModule ( ) , ] )

A full counter application can be found here on Github.

A moduleName and defaultState are set within the class, and a single generator function *handleCounter is defined, which simply increments the counter by 1.

The public and private keywords are used to show which class methods are callable from outside of the class itself. For more about this feature of TypeScript, see their handbook about classes. In this case, getCounter is marked private so that only CounterModule has access to its own state.

This is much less code than the first example. This makes it more maintainable and easier to reason about. Overall, this leaves you with a cleaner codebase.

In your presentational component, this is how you might call *handleIncrement :

const { counter } = sitka . getModules ( ) < button onClick = { counter . handleIncrement } > INCREMENT < / button >

The handleIncrement generator defined in your module is wrapped in an Action that is created under the hood, because its name starts with handle .

What’s happening under the hood

Sitka spares you from writing boilerplate by autogenerating code. It generates:

An action wrapping each generator function

A reducer backing the setState method

A section of the redux state tree, defined by the sitka class attribute moduleName - in this case counter .

When should you use Sitka

We recommend using Sitka if you intend to build a larger application using typescript and redux. Sitka keeps your code strongly typed and organized, while reducing the boilerplate burden of that redux and typescript can add out of the box. Less typing = faster development time.

Upgrading existing applications

Though you can build your application from the ground up using Sitka, it's also designed to be incrementally added to an existing Redux application. See an example of adding Sitka into a project, and an example of usage using React-Redux's connect function to connect your component to a Sitka-powered Redux store .

Olio Apps is using Sitka

We are using Sitka in live projects, and it's delightful to work with. We get to take advantage of all the benefits of a strongly-typed codebase, while also enjoying writing a small fraction of the code we needed before using Sitka. We at Olio Apps hope you try out Sitka to manage state in your next project, or even to implement a new feature in your existing project.