npm

@boninger-works/state
TypeScript icon, indicating that this package has built-in type declarations

10.1.0 • Public • Published

State

Build Status

Library for defining, updating, and accessing data models in a reactive and type-safe manner.

Because JavaScript is not a strongly-typed language, this library is intended to be used with TypeScript.

Installation

npm install @boninger-works/state

Usage

Add core imports.

import { State, IState, IStateReadOnly }
	from "@boninger-works/state/library/core";

Instantiating

Define an interface to represent the state.

interface IOrder {
	items: string[];
	shipping: {
		strategy: "ShipItemsTogether" | "ShipItemsImmediately";
		speed: "OneDay" | "TwoDay" | "Standard";
	};
	offerCode?: string;
	giftWrap?: {
		giftReceipt: boolean;
		personalMessage: string;
	};
}

Create the generic state instance based the interface.

const state = State.create<IOrder>({
	items: [],
	shipping: {
		strategy: "ShipItemsTogether",
		speed: "Standard"
	}
});

Subscribing

Subscribe to the entire state

const observable = state.observe();
observable.subscribe(order => {
	console.log(order, "ENTIRE_STATE");
});

Subscribe to part of the state. The "items" string below is strongly typed (not a magic string!).

const observable = state.to("items").observe();
observable.subscribe(items => {
	console.log(items, "ONLY_ITEMS");
});

Subscribe to a nested part of the state. Note that the to() method can be chained to continue to define the desired path.

const observable = state.to("shipping").to("speed").observe();
observable.subscribe(shipSpeed => {
	console.log(shipSpeed, "ONLY_SHIP_SPEED");
});

Handling Undefined

Keys provide return types that are aware of possibly incomplete paths. This occurs if an ancestor of the desired property can potentially be undefined or null. As a result, the keys type will reflect this by adding undefined as part of the generic type.

Without TypeScript's strictNullChecks compiler option enabled, this effect is not visible because anything can always be undefined or null.

const path = state.to("giftWrap").to("giftReceipt");
const observable = path.observe();
observable.subscribe(giftReceipt => {
	console.log(giftReceipt, "GIFT_RECEIPT_OR_UNDEFINED");
});

However, it can be desirable to deal with only defined values. In this case, the defined() operator can be used to filter the observable created from the keys.

Add operator imports.

import { defined }
	from "@boninger-works/state/library/operators";
const path = state.to("giftWrap").to("giftReceipt");
const observable = path.observe().pipe(defined());
observable.subscribe(giftReceipt => {
	console.log(giftReceipt, "GIFT_RECEIPT");
});

Getting

Get the state. Returns the current version of the state.

const order = state.get();
console.log(order);

Updating

Update the entire state.

state.set({
	items: [],
	shipping: {
		strategy: "ShipItemsImmediately"
		speed: "OneDay"
	}
});

Update part of the state.

state.to("items").set([ 
	"popularBook",
	"goodMovie",
	"sweetMusic"
]);

Batch update the state.

state.batch(batch => {
	batch.to("offerCode").set("EVERYTHING4FREE");
	batch.to("giftWrap").set({
		giftReceipt: true,
		personalMessage: "Here is something special for you!"	
	});
});

Transforming

Add transform imports.

import { 
	push, unshift, pop, shift, filter, insert, remove,
	increment, decrement, add, subtract, multiply, divide,
	set, unset
}
from "@boninger-works/state/library/transforms";

Tranform part of the state. In this case, add an item to the array of items.

state.to("items").transform(push("tastyTreat"));

Opting Out of Type-Safety

If the need arises, keys can be created in a non-type-safe manner. Note that the keysUnsafe method takes a single array argument, which is different from the keys method, which builds type-safe keys.

const keysUnsafe = state.keysUnsafe(["one", "two", "three"]);
const observableUnsafe = state.path(keysUnsafe).observe();
observable.subscribe(anything => {
	console.log(anything, "ANYTHING");
});

Remarks

Emissions

When the observe() method is used to subscribe to part of a state, the returned observable is automatically configured in the following ways:

  1. It will immediately emit the current value for the defined path.
  2. It will emit any future changes to that value.

The observable is configured to act only on the specific slice of the state that was requested. For instance, if items is subscribed to in the above examples, then updates to the shipping property of the state will not emit for the items subscriber because the value of items has not changed.

Batching

When batching updates, the state batcher is immediately updated with each call to the set() or transform() method, but the value is not set and emitted for the state being acted on until the very end. Batching can be used to solve one of the following two problems:

  1. Small frequent updates are causing too many emissions to subscribers.
  2. Individual updates would make a state emit an object which is not correct until other updates are also applied.

Batch operations can also be emitted on demand in the middle of the batch using the emit() method.

The current value of the batch state can be retrieved with get() during the batch operation.

Read Only

The IStateReadOnly interface provides read-only state functionality. This is available through the readOnly property on IState. It is preferable to use the IStateReadOnly stored on the readOnly property because it has better protection from unwanted writes than IState itself.

Immutability

The intent of the state is always to avoid mutating references that are stored within it. Methods provided by this library will always create shallow copies of reference types to avoid mutations while avoiding unnecessary memory allocations for references that do not need modification. However, it is also important to understand that reference types retrieved from the state do not have any inherent protection. Enforcing or respecting immutability of these references is the responsibility of the the implementer in this case, because the state cannot control the generic type that is provided to it.

Package Sidebar

Install

npm i @boninger-works/state

Weekly Downloads

39

Version

10.1.0

License

ISC

Unpacked Size

55.7 kB

Total Files

42

Last publish

Collaborators

  • jbonman