Medama is a minimalist yet powerful universal reactive state management library crafted in TypeScript. It boasts high performance optimization and efficient garbage collection. At its core, medama operates on four fundamental concepts: state, selectors, subscriptions, and setters.
npm i medama
In medama, the state is represented by a streamlined flat object, with each key holding a distinct state record. The journey begins with initializing medama,
const { readState, subscribeToState, setState, resetState, pupil } = createMedama<State>();
and conveniently, the state can be set up during this creation phase.
const initState: Partial<State> = { foo: 'foo_record', bar: 'bar_record' }
const { readState, subscribeToState, setState, resetState, pupil } = createMedama<State>(initState);
Selectors serve as the primary interface for reading the state and subscribing to state changes.
const selector = ({ foo, bar }: State) => foo + bar;
readState(selector); // 'foo_record bar_record'
They offer a refined method to access individual records from the state, ensuring precise data retrieval.
readState(({ foo }) => ({ foo })); // { foo: 'foo_record' }
Subscribing to state changes is achieved by pairing a selector with a subscription. In its simplest form, a subscription manifests as a subscription job.
subscribeToState(selector, (value) => {
console.log(value);
});
// print 'foo_record bar_record'
By default, the subscription job activates immediately upon establishment. For cases where immediate execution isn't desired, the subscription can be defined as a function that returns the subscription job.
subscribeToState(selector, () => (value) => {
console.log(value);
});
// no side effect
medama also offers the flexibility to define separate jobs: one that runs at the moment of subscribing, and another that registers as an ongoing subscription job.
subscribeToState(selector, (value) => {
console.log('This is the first run with value:', value)
return (value) => {
console.log(value);
};
});
// print 'This is the first run with value: foo_record bar_record'
The subscription process establishes a dependency between the subscription job and the specific state records that the selector relies upon.
State updates in medama are handled through a smooth merging process of an object into the state object. The most straightforward approach is to directly pass the object for merging.
setState({ foo: 'new_foo' });
readState(({ foo, bar }) => ({ foo, bar })); // { foo: 'new_foo', bar: 'bar_record' }
Alternatively, a setter can be employed. Similar to a selector, a setter takes the state object as a parameter but returns an object destined for merging into the state.
setState(({ foo, bar }) => ({ foo: 'next_' + foo, bar: 'next_' + bar }));
readState(({ foo, bar }) => ({ foo, bar })); // { foo: 'next_new_foo', bar: 'next_bar_record' }
Any modifications to a single state record trigger the execution of subscription jobs that are subscribed to selectors depending on that record, ensuring targeted and efficient reactive updates.
subscribeToState(selector, (value) => {
console.log(value);
})
setState({ bar: 'very_new_bar' });
// print 'next_new_foo very_new_bar'
Occasionally, a complete state reset is necessary. The resetState
method caters to this need,
allowing for a fresh start. During this reset, the state can be initialized with new values if
desired.
resetState(initState);
It's important to note that resetting the state dissolves all existing subscriptions, providing a truly clean slate.
The createMedama
function yields a pupil
object, which encapsulates all available methods.
const { readState, subscribeToState, setState, resetState } = pupil;
The name "Medama" draws inspiration from the Japanese word 目玉, meaning "eyeball," with the pupil
representing the core of medama. This pupil
object is designed to facilitate the development of
supporting libraries. Moreover, the pupil
object can be passed as an opaque object within these
libraries, enabling the establishment of interconnected logic based on state updates.
Medama's design prioritizes efficiency. It recalculates selector values only when dependency records have been updated and active subscriptions exist for that selector. In scenarios with multiple subscriptions or the need for arbitrary state reading, the selector's calculated value is memoized following the most recent state update, avoiding unnecessary recalculations.
This optimization hinges on selector identity consistency. For the memoization to function correctly, the same selector instance must be used, rather than just identical function literals. This design choice influences how medama determines state record dependencies. Upon the first subscription establishment, the selector's value is calculated to identify its dependencies. For subsequent subscriptions, if the dependency records remain unchanged and the selector instance is consistent, medama skips recalculation, leveraging the existing results.
Importantly, medama provides an additional layer of safety. The state object passed to selectors and setters, if captured, does not allow direct access to the state. This access is restricted to within medama itself during the processing of selectors and setters, ensuring data integrity and preventing unauthorized state modifications.