Skip to main content

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

testing

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:

types.ts
import { Atom } from 'nrgy';
import { ViewModel } from 'nrgy/mvc';

export type CounterViewModelType = ViewModel<{
props: { initialValue: Atom<number> };

state: { counter: Atom<number> };

increase(): void;
}>;
counter.viewModel.ts
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),
};
},
);
counter.view.tsx
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()} />
</>
);
};
Counter.tsx
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);