JSPM

  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q78759F
  • License MIT

Reactive State Management Powered by RxJS

Package Exports

  • juliette

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (juliette) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Juliette Logo

Juliette · MIT License NPM Version Downloads

Reactive State Management Powered by RxJS

Table of Contents

Description

Juliette is a reactive state management library inspired by NgRx. It reduces Redux boilerplate, eliminates reducer's conditional branching, simplifies the configuration and introduces NgRx architecture into the framework-agnostic world. Juliette is a TypeScript friendly library and can be used in Angular, React or any JavaScript application.

Reduced Boilerplate Without Reducer's Ifology

Juliette reduces Redux boilerplate by merging action and reducer into one component called handler. To better understand the benefits of handler, let's first look at how actions and reducers are defined by using NgRx.

Old NgRx Approach
// users.actions.ts

export const FETCH_USERS = '[Users] Fetch Users';
export const FETCH_USERS_SUCCESS = '[Users] Fetch Users Success';
export const FETCH_USERS_ERROR = '[Users] Fetch Users Error';

export class FetchUsers implements Action {
  readonly type = FETCH_USERS;
}

export class FetchUsersSuccess implements Action {
  readonly type = FETCH_USERS_SUCCESS;

  constructor(public payload: User[]) {}
}

export class FetchUsersError implements Action {
  readonly type = FETCH_USERS_ERROR;
}

export type Action = FetchUsers | FetchUsersSuccess | FetchUsersError;

// users.reducer.ts

import * as UsersActions from './users.actions';

export interface State {
  users: User[];
  showLoading: boolean;
}

const initialState: State = {
  users: [],
  showLoading: false,
};

export function reducer(state = initialState, action: UsersActions.Action): State {
  switch (action.type) {
    case UsersActions.FETCH_USERS:
      return { ...state, showLoading: true };
    case UsersActions.FETCH_USERS_SUCCESS:
      return { ...state, users: action.payload, showLoading: false };
    case UsersActions.FETCH_USERS_ERROR:
      return { ...state, users: [], showLoading: false };
    default:
      return state;
  }
}

TypeScript code above shows the old NgRx syntax and it is pretty similar to traditional Redux approach. As you can see, it's too much code for three simple actions. Then, NgRx team introduced a new way to define actions and reducers.

New NgRx Approach
// users.actions.ts

export const fetchUsers = createAction('[Users] Fetch Users');
export const fetchUsersSuccess = createAction(
  '[Users] Fetch Users Success',
  props<{ users: User[] }>(),
);
export const fetchUsersError = createAction('[Users] Fetch Users Error');

// users.reducer.ts

import * as UsersActions from './users.actions';

export interface State {
  users: User[];
  showLoading: boolean;
}

const initialState: State = {
  users: [],
  showLoading: false,
};

export const reducer = createReducer(
  initialState,
  on(UsersActions.fetchUsers, state => ({ ...state, showLoading: true })),
  on(UsersActions.fetchUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    showLoading: false,
  })),
  on(UsersActions.fetchUsersError, state => ({
    ...state,
    users: [],
    showLoading: false,
  })),
);

With new NgRx syntax, less amount of code is needed to define actions and reducer. Conditional branching for actions in the reducer is masked by the on operator, but it is still present. Let's now look at how the same example is implemented using Juliette handlers.

Juliette Approach
// users.handlers.ts

export const stateKey = 'users';

export interface State {
  users: User[];
  showLoading: boolean;
}

export const initialState: State = {
  users: [],
  showLoading: false,
};

export const fetchUsers = createHandler<State>(
  '[Users] Fetch Users',
  stateKey,
  state => ({ ...state, showLoading: true }),
);
export const fetchUsersSuccess = createHandler<State, { users: User[] }>(
  '[Users] Fetch Users Success',
  stateKey,
  (state, { users }) => ({ ...state, users, showLoading: false }),
);
export const fetchUsersError = createHandler<State>(
  '[Users] Fetch Users Error',
  stateKey,
  state => ({ ...state, users: [], showLoading: false }),
);

As you can see, Juliette way is more declarative. Also, the least amount of code is required to define the same logic. Instead of creating actions and then adding new conditional branches to the reducer, Juliette's handler creator accepts the reducer function on-site.

Simplified Configuration

You don't need to register reducers to the store anymore!

Framework Agnostic

Core features of Juliette are implemented in pure TypeScript. The library is small in size and has RxJS as the only production dependency. All framework specific stuff is in separate libraries. There are two plugin libraries available, for Angular and for React They provide core functionalities adapted to the framework design. Of course, Juliette can be used in Angular or React without plugins, but that way wouldn't be native.

Architecture

Juliette doesn't have a much less complex execution flow than NgRx, but one part of the architecture is different. Merging action and reducer into handler will reduce the boilerplate and will not make a mess in complex systems. Let's look at the diagram.

Juliette Architecture

When an event occurs on the view, it will dispatch the handler. Then, if the handler has a reducer function, it will be executed by the store and new state will be reflected in the view. After that, if the handler has a side effect, that effect will be performed. Lastly, if the effect returns a new handler, the execution process will be repeated.

Installation

Run npm install --save juliette to install core Juliette library.

If you are using Angular, install additional package by running npm install --save juliette-ng command.

If you are using React, install additional package by running npm install --save juliette-react command.

Usage

UNDER CONSTRUCTION

Handlers

Store

Effects

Angular Plugin

React Plugin

Quick Start with Angular

Quick Start with React

Support

Give a ⭐ if you like Juliette 😎

License

Juliette is MIT licensed.

Copyright © 2020 Marko Stanimirović