@kamyil/reactive-function
    TypeScript icon, indicating that this package has built-in type declarations

    1.5.4 • Public • Published

    Reactive Function

    The one simple function that allows you to make your values reactive to each other!

    NPM https://www.npmjs.com/package/@kamyil/reactive-function Link to NPM Package

    DEMO: https://stackblitz.com/edit/typescript-maqe1v?file=index.ts

    Advantages

    • Minimalistic
    • Very Light-weight
    • 100% Hardly Typed
    • Framework agnostic
    • Zero dependencies

    (DISCLAIMER: This package is mainly my side project created and maintained purely for fun. It is battle tested and can be used on production freely, but... please. Keep in mind that this project can be, but should not be used as an default reactive system if you're planning to create serious business-valuable project for either yourself or company you work for, since there are various of JavaScript frameworks already that are way more advanced. Also if you're looking for simpler but framework-agnostic reactivity system, I would rather recommend @vue/reactivity (https://www.google.com/search?client=safari&rls=en&q=%40vue%2Freactivity&ie=UTF-8&oe=UTF-8). Consider this library being a last-ditch alternative)

    OFC: feel free to suggest any changes in pull requests or raise an issue in Issues tab 😅

    How to use it?

    First import the function (since it's written in TypeScript, you can auto-import it);

    import { reactive } from '@kamyil/reactive-function';

    if you receive the error from your bundler/compiler that it cannot get the module (f.e. Vite can do that, because it points automatically on types.d.ts file instead of index.ts file), then import it this way

    import { reactive } from '@kamyil/reactive-function/index';

    or you can destructure it using require if you do not use any kind of module bundler for writing your Node application

    const { reactive } = require('@kamyil/reactive-function');
    // or
    const { reactive } = require('@kamyil/reactive-function/index');

    Then pass your value into reactive function and assign it's result to variable

    import { reactive } from '@kamyil/reactive-function';
    
    const myReactiveValue = reactive('some random string');

    And from now on you can get always-freshly updated value by accessing it's value property

    or update it by mutating value property as well

    import { reactive } from '@kamyil/reactive-function';
    
    const myReactiveValue = reactive('some random string');
    
    console.log(myReactiveValue.value); // => 'some random string'
    myReactiveValue.value = 'some another string';
    console.log(myReactiveValue.value); // => 'some another string'

    What's so reactive about that huh? It's obvious that if you mutate properties, you will update and retrieve only fresh values

    You're absolutely right! But now the magic is going to happen. Let's say: now you want to make it reactive to other reactive value

    import { reactive } from '@kamyil/reactive-function';
    
    const myNumber = reactive(2);
    // and now let's make it dependent from number above
    const anotherNumber = reactive(() => myNumber.value * 2);
    
    // and now let's check if it will react on first reactive value change
    myNumber.value = 4;
    
    console.log(myNumber.value); // => 4
    console.log(anotherNumber.value); // => 8

    Voila!, it does react. But you may ask 'Why there is a function in second reactive variable?'

    The answer is actually pretty simple: JavaScript unlike Lazy computed languages like Haskell, it does compute expressions early before any function calls. So this kind of expression

    const myNumber = reactive(2);
    const anotherNumber = reactive(myNumber.value * 2);

    ... will early and automatically compute myNumber.value * 2 into simple 4 without saving any reference to myNumber variable. Thanks to passing function, we can save all references to all used variables in that function and then use those variables to compute always fresh and updated value as a result.

    How does it work?

    All of your reactive values live inside $reactiveDataContainer variable that sits inside window or global object (depending if you're running this function in a browser or Node.js) and those reactive values retrieve, compute and save references in there. When .value is called, then the getter function runs

    • checks all dependencies of itself
    • gets values from them
    • and computes new value

    However when f.e. the someReactive.value = 'some new value' is being called, then it runs the setter function that will update the value, update computing function and also trigger/call other dependent values to react and update theirselves. All thanks to JavaScript Proxy API https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy that allows to override default JavaScript behaviour to achieve such a thing 👏

    What about objects and arrays?

    They can also be used in the same way like primitive values

    let testArray = reactive([1, 2, 3]);
    let testObject = reactive({
      name: 'Kamil',
      age: 23,
    });

    However, if you want to update it YOU CANNOT MUTATE IT DIRECTLY. You have to always pass new values to them, because otherwise you will loose all of previous used data

    testArray.value = [...testArray.value, 4];
    testObject.value = {
      ...testObject.value,
      favoriteHero: 'Daredevil',
    };

    How to track changes?

    For this case there is a special tool function called trackChanges. You can use it to perform your actions on every reactive value change

    import { reactive, trackChanges } from '@kamyil/reactive-function';
    
    const testNumber1 = reactive(1);
    
    trackChanges(testNumber1, ({ previousValue, newValue }) => {
      // here will be value before update
      console.log(previousValue); // => 1
      // here will be value after update
      console.log(nextValue); // => 2
    });
    
    testNumber.value = 2;
    • first argument: reactive value to track
    • second argument: callback to perform on every change

    How to stop tracking changes?

    However if you want to stop tracking changes, you can import another tool function called stopTracking. When called, it will stop tracking changes, not perform any passed callback into trackChanges function anymore, and if any callback for stopTracking function was passed - it will call it once.

    import {
      reactive,
      trackChanges,
      stopTracking,
    } from '@kamyil/reactive-function';
    
    const birthday = reactive(1);
    
    trackChanges(testNumber1, ({ newValue }) => {
      if (newValue === 18) {
        stopTracking(testNumber1, () =>
          alert(`Congratulations! You're an adult now!`)
        );
      }
    });
    
    setInterval(() => birthday.value++, 1000);

    How to sync reactive variables with HTML?

    There is a dedicated tool function for that case called syncWithHTML

    import { reactive, syncWithHTML } from '@kamyil/reactive-function';
    
    const myNumber = reactive(1);
    const doubledNumber = reactive(() => myNumber.value * 2);
    
    // And it will automatically reflect changes into your DOM Element
    syncWithHTML(doubledNumber, '.element-to-sync');

    However, if you want more flexibility with auto-updating HTML, then folow this recommendation.

    As mentioned before, you can grab always fresh value by getting value property of your reactive variable like so: yourVariable.value. You can combine it with tool function called trackChanges to update the DOM Element your own way. Since trackChanges accepts a callback as a second argument - there you have flexibility to perform any side-effects you want

    For TypeScript users

    Since this library is heavly using TypeScript and it's glorious features to embrace developer's productivity and hapiness, expect type inference in almost every place possible thanks to TypeScript Generics.

    However if you want to type everything manually on your own, you can pass your types/interfaces as a generic to this function

    type Person = {
      name: string;
      age: number;
      favoriteHero?: string;
    };
    
    testObject = reactive<Person>({
      name: 'Kamyil',
      age: 24,
    });

    The same goes for tool functions like trackChanges, stopTracking and syncHTML;

    When to pass value and when to pass callback?

    As mentioned earlier. Pass values directly ONLY if they will not be dependent from any other reactive values. Otherwise, put them after the lambda () => yourVariable.value in order to save reference for your variables and produce always fresh values

    I have property $reactiveDataContainer does not exist on type (Window & typeof globalThis) problem

    It means that your development environment did not catch extended Window & Global interfaces with this property. The possible fix for that would be adding it manually to your type definition file

    import { IReactiveDataContainer } from '@kamyil/reactive-functions';
    
    declare global {
      interface Window {
        $reactiveDataContainer: IReactiveDataContainer;
      }
      namespace NodeJS {
        interface Global {
          $reactiveDataContainer: IReactiveDataContainer;
        }
      }
    }

    Inspirations

    Install

    npm i @kamyil/reactive-function

    DownloadsWeekly Downloads

    3

    Version

    1.5.4

    License

    MIT

    Unpacked Size

    42.5 kB

    Total Files

    23

    Last publish

    Collaborators

    • kamyil