Nrgy.js
Energy for reactive programming
State and effect management with MVC/MVVM patterns
Overview
The library provides components for programming with reactive state and effects using MVC/MVVM design patterns.
Core components form an efficient computation graph which includes:
- Atom - a state store
- Signal - an event emitter
- Effect - a subscription to Atom or Signal
- Scope - a sink of subscriptions
The library has tools for MVC/MVVM design pattern to program Controller as business logic and View Model as a presentation layer independently of UI framework.
Controllers and view models can be extended to use additional features, as example, to use a dependency injection container.
Features
Easy to Use
The library is designed to be easily installed and used to get your application up and running quickly
Reactive state and effects
Fully reactive state and effects using well-known concepts like atoms
and signals
Fast computations
The efficient engine combines all computable nodes into a reactive graph
Framework-agnostic Core
It can be used by web and server applications, libraries and CLI tools
MVVM and MVC patterns
Bring business logic to the frontend with view models and controllers
Ready for GC
The loose coupling between internal parts allows automatic deletion of components that are no longer in use
Developer-friendly API
Functional programming and object-oriented programming are both supported
Type-safety
The library is written in Typescript and provides type-checking and autocomplete for your code
100% test coverage
The library has been fully tested and has complete code coverage
Example code
A picture is worth a thousand words
Core toos
Reactive state and effects:
import { atom, compute, effect, signal } from 'nrgy';
const name = atom<string>('World');
const greetings = compute(() => `Hello ${name()}!`);
console.log(greetings());
// console: Hello World!
name.update((value) => value.toUpperCase());
effect(greetings, (value) => console.log(value));
// console: Hello WORLD!
const changeName = signal<string>();
effect(changeName, (nextValue) => name.set(nextValue));
changeName('UserName');
// console: Hello UserName!
MVVM pattern
Separating presentation logic from a view:
import { Atom } from 'nrgy';
import { ViewModel } from 'nrgy/mvc';
export type CounterViewModelType = ViewModel<{
props: { initialValue: Atom<number> };
state: { counter: Atom<number> };
increase(): void;
}>;
import { declareViewModel } from 'nrgy/mvc';
import { CounterViewModelType } from './types.ts';
export const CounterViewModel = declareViewModel<CounterViewModelType>(
({ scope, view }) => {
const { initialValue } = view.props;
const counter = scope.atom(initialValue());
scope.effect(counter, (value) => {
// Performing a side effect
console.log('counter', value);
});
return {
state: { counter: counter.asReadonly() },
increase: () => counter.update((prev) => prev + 1),
};
},
);
import { useAtoms } from 'nrgy/react';
import { FC } from 'react';
import { CounterViewModelType } from './types.ts';
export const CounterView: FC<{
viewModel: CounterViewModelType;
label: string;
}> = ({ viewModel, label }) => {
const { value } = useAtoms(viewModel.state);
return (
<>
<span>
{label}: {value}
</span>
<button onClick={() => viewModel.increase()} />
</>
);
};
import { withViewModel } from 'nrgy/mvc-react';
import { CounterView } from './counter.view.tsx';
import { CounterViewModel } from './counter.viewModel.tsx';
// HOC component combines the view and the view model
export const Counter = withViewModel(CounterViewModel)(CounterView);