educe
v1.0.3
Published
React state management library with history and persistence functionality
Downloads
5
Readme
Educe.js
Educe is a simple at its interface, yet powerful state management solution for react, the OOP and react hooks way, built with TypeScript. Uses the same API as React class based component, with the immutable pattern. Its reason to exist is a boilerplate-free code with zero cost abstractions.
Consists of:
- Store - base class for your store and its history.
- Stream - base class for single value stream. (AsyncIterable)
- useStore - hook for state access.
- withStore - HOC/decorator for state access.
- useStream - hook for stream value access.
- Persistence - class with static methods as state persistence helpers.
- Store.Persisted - decorator for state persistence.
Store
- Observable base class with history functionality.
- Manage its lifetime with decorators. SINGLETON, LAZY_SINGLETON, SHARED (for example you can share a Store for a specific screen among its children, and be destroyed when navigate in another screen) and TRANSIENT.
- Can be used independently.
- State subscriptions inside components happen implicitly by using js proxies.
- You can also override requestEffects and requestCleanup as lifecycle methods.
- You can attach lifecycle observers by implementing the IStoreLifecycleObserver interface. (with addLifecycleObserver method) or add observers for all stores (with Store.addGlobalLifecycleObserver method)
Stream
AsyncIterable base class for single value observation. Used independently or provided in useStream hook. Subscriptions happen implicitly.
useStore
React binding hook for state access.. Provide the class of the store. Returns the state. Listens to any prop accessed by the returned state and only those. You can explicitly provide the props to listen. You can call requestEffects and requestCleanup as lifecycle methods by specifing the withEffects argument. Can be used to access the state only one time by specifing the listen argument.
withStore
React HOC for state access. Same functionality as useStore but the hoc style. TS: used only as decorator
useStream
React binding hook for state access. Also implicit subscription.
Persistence
Used as namespace for providing persistence utils.
Persistence.Persisted
- Decorator/hof for Store subclasses.
- Persists state on each setState call.
- If provided database is synchronous, store gets instantiated with persisted state.
- If database is asynchronous, store gets instantiated with initial state,
- and when state is fetched, setState is internally called and updated.
- provide param StatePersistedConfig.
- databaseName: is used concatenated for storage key
- databaseVersion: versioning for the current Store. if version changes, old stored state is automatically, deleted.
- Can delete manually from Persistence.clearStorePersistedState
- database: default is localStorage, can set it here or can set default database from static Persistence.setDefaultDatabase
- transformers: transforms state before its saving and after its fetch.
- Use Persistence.createTransform, to pass function transformers for set and get database transactions.
Basic Store Example usage
// ExampleStoreState.ts
export interface ExampleStoreState {
count: number;
followers: string[]
}
// ExampleStoreEvents.ts
export enum ExampleStoreEventType {
Add,
Deduct
}
export type ExampleStoreEvents = StoreEvents<{
CountAddEvent: { type: ExampleStoreEventType.Add, valueToAdd: number; }
CountDeductEvent: { type: ExampleStoreEventType.Deduct, valueToDeduct: number; }
}>
// ExampleStore.ts
import {Store} from "educe";
@InjectableStore(Lifetime.SINGLETON)
export class ExampleStore extends Store<ExampleStoreState, ExampleStoreEvents> {
protected state: ExampleStoreState = {
count: 0,
followers: []
};
public requestEffect() {
console.log("fetching something...");
}
public requestCleanup() {
console.log("close some websockets...");
}
public mapEventToState(event: ExampleStoreEvents): void {
switch (event.type) {
case ExampleStoreEventType.Add:
this.setState({count: this.state.count + event.valueToAdd})
break;
case ExampleStoreEventType.Deduct:
this.setState({count: this.state.count - event.valueToDeduct});
break;
default:
unreachableEvent(event);
}
// The code bellow throws when Store.addGlobalLifecycleObserver(new StateDeepFreezer())
// this.state.followers.push("new follower")
}
public somePublicMethod(): void {
console.log("does something...");
}
}
// App.tsx
function App() {
const [{count}, store] = useStore(ExampleStore, {withEffects: true});
return (
<div style={style}>
<h1>Parent Count: {count}</h1>
<button onClick={() => store.addEvent({type: ExampleStoreEventType.Add, valueToAdd: 10})}>Parent add
</button>
<Child/>
</div>
);
}
function Child() {
const [{count}, store] = useStore(ExampleStore);
return (
<div>
<h5>Child Count: {count}</h5>
<button onClick={() => store.addEvent({type: ExampleStoreEventType.Deduct, valueToDeduct: 10})}>Child
subtract
</button>
</div>
)
}
Store state can be used outside react components
const listener = (state: ExampleStoreState) => console.log(state.count);
exampleStore.subscribe("count", listener);
exampleStore.unsubscribe(new Set(["count"]), listener);
Prevent mutations of state when on development
// index.tsx
// Detect and throw for state mutations only in development
if (process.env.NODE_ENV === 'development')
Store.addGlobalLifecycleObserver(new StateDeepFreezer())
Basic Stream Example usage
// ThemeStream.ts
import {Stream} from "educe";
export enum Theme {
Light = "light",
Dark = "dark"
}
export class ThemeStream extends Stream<Theme> {
constructor() {
super(true);
}
get initialValue(): Theme {
return Theme.Dark
}
public toggleTheme = () => {
this.nextValue(this.getValue() === Theme.Dark ? Theme.Light : Theme.Dark);
}
}
export const themeStream = new ThemeStream();
// Theme.tsx
import {useStream} from "educe";
const Theme = () => {
const theme = useStream(themeStream);
return (
<div>
<h5>Theme: {theme === Theme.Dark ? "dark" : "light"}</h5>
<button onClick={themeStream.toggleTheme}>Toggle theme</button>
</div>
);
};
Stream value can be used outside react components
//With subscription
const listener = (isOpen: boolean) => console.log(isOpen);
exampleStream.subscribe(listener);
exampleStream.unsubscribe(listener);
// or as AsyncIterable
for await (const isOpen of exampleStream) {
console.log(isOpen);
}
Future
- Better documentation.
- Tests.
Installation
As of now, Educe.js requires React >=16.8.0+ to run. (The one with hooks)
$ npm install educe --save
License
MIT