Minimalist Virtual DOM library with JSX and factory pattern for stateful components.

Related tags

Video/Audio reflex
Overview

Reflex

Reflex JS is a tiny ~3kb virtual-dom library with factory based functional components.


Table of contents

Be kind, Lib and doc are still in beta 😘


Concept

Stateful components will return a render function instead of virtual-nodes directly. Scope is shared between the factory and the render function.

function FactoryComponent ( props ) {
    // factory hooks and component logic goes here
    // render function and conditions goes there
    return () => <div>...</div>
}

Classic React Hooks like useCallback, useEvent and useMemo becomes useless. Also, hooks dependencies array to keep state scopes (#1 #2) does not exist with Factory Hooks. Using useRef to store stateless values does not exist anymore. In Reflex, ref are only here to target dom node or components, let is used to declare local variables like it would normally do.

How to install

Install it with npm i @zouloux/reflex. You will need at least those options into tsconfig.json :

{
    "compilerOptions": {
        "jsxFactory": "h",
        "jsx": "react"
    }
}

Because code samples are better than a thousand words

Simple DOM rendering

// Import Reflex like you would import Preact for example.
import { h, render } from "reflex";

function renderApp( greetings:string ) {
  const app = <div class="MyApp">
    <h1>{ greetings }</h1>
  </div>
  render( app, document.body )
}

renderApp( `Hello from Reflex ✌️` )
// Note : if you call renderApp again, it will update state of previously rendered app
// renderApp( `Dom is updated` )

Stateless and pure components

Stateless, or components without logic can avoid the factory pattern. Simply return the virtual-dom tree derived from props like you would do it in React or Preact.

function StatelessComponent ( props ) {
    return <div class="StatelessComponent">
      Hello { props.name } πŸ‘‹
    </div>
}

Because Stateless and Stateful components are written differently, Reflex can optimize render of Stateless components by keeping old virtual-node tree, if props did not change between renders. We have better performances without adding anything to our app.

function ChangingComponent ( props ) {
  // If "connectedUser.name" does not changes between "ChangingComponent" renders,
  // "StatelessComponent" does not need to re-render.
  return () => <div>
    ...
    <StatelessComponent name={ connectedUser.name } />
  </div>
}

Set <StatelessComponent name={ connectedUser.name } pure={ false } /> if your stateless component isn't a pure function (if it uses some other dependencies than its props).

Stateful components with factory pattern

This is where it changes from React. Stateful components in Reflex follows the Factory Component Pattern. Factory hooks are used only in the "factory phase" of the component.

function StatefulComponent ( props ) {
    // This is the "factory phase"
    // This part of the component is executed once, when component is created and not updated.
    
    // Create a state for this component, like in React or Solid 
    const currentNumber = state( 0 )
    const incrementNumber = () => currentNumber.set( currentNumber.value + 1 )
    
    // The component needs to return a function which will render the component
    return () => <div class="StatefulComponent">
        {/* Update state when button is clicked */}
        <button onClick={ incrementNumber }>
            Click to increment current number: {currentNumber.value}
        </button>
    </div>
}

States are based on Observable, which is an internal dependency of Reflex. Observable is based on Signal, which is the only external dependency of Reflex (~300 bytes). The UNPKG bundle inlines the Signal package as an internal dependency to be standalone.

Props

In Stateful components, "props" is a Proxy object (like in Solid). Because factory phase is executed once, at component's creation, we need a way to access new props values at each render, this is possible thanks to Proxy #1, #2.

function PropsComponent ( props ) {
    function logName () {
        // βœ… Will log latest name, even if component rendered several times
        console.log( props.name )
    }
    return () => <div>
        <button onClick={ logName }>Log name</button>
    </div>
}

The main tradeoff is that props destructuring is not possible anymore. Or destructed props will be equal to the first props value and will never change.

function PropsComponent ( props )  {
    // 🚫 Here name will never change even if the component is updated by its parent
    const { name } = props
    function logName () {
        console.log( name )
    }
    return () => <div></div>
}

Factory hooks

Here is a list of all base factory hooks available. Remember, factory hooks are only usable in factory phase of components and not available in Stateless components. Like in React, factory hooks are composable into other functions easily.

State

// Create a new state
const myState = state( initialState )

// Get current state value
console.log( myState.value )

// Set new value (will trigger a component update)
myState.set( newValue )

Note, setting a new state is asynchronous because all state changes of a component are stacked and component renders only once for better performances. After the component is refreshed, the await state.set( value ) promise will be resolved.

Ref

Like in React, we can use ref to target rendered components.

function MyComponent () {
    const otherComponentRef = ref()
    function showref () {
        // Log component dom element
        console.log('DOM', otherComponentRef.dom )
        // Log component instance
        console.log('Component', otherComponentRef.component )
    }
    return () => <div>
        <OtherComponent ref={ otherComponentRef }/>
        <button onClick={ showref }>Show ref</button>
    </div>
}

The main difference with React is that ref are useless to create locally scoped component variables.

To create a locally scoped prop that will not trigger rendering, just use let

function MyComponent () {
    let localVariable = 0
    function updateLocalVariable () {
        localVariable ++
        console.log( localVariable );
    }
    return () => <div>
        <button onClick={ updateLocalVariable }>Update local variable</button>
    </div>
}

Refs aka multi-ref

Multi ref in Reflex is ref as an array of components. Very handy when dealing with lists !

function List ( props ) {
    const itemRefs = refs()
    function showListItemElements () {
        // Will be an array of all refs
        console.log( itemsRefs.list );
    }
    return () => <ul>
        {props.items.map( item => <li ref={itemRefs}>{item.name}</li> )}
    </ul>
}

Refs are populated in order of rendering. So if you are using a list which can render in another order than from 0 to length, you can specify the index ( see example )

function List ( props ) {
    const itemRefs = refs()
    return () => <ul>
        {props.items.map( (item, i) =>
            // Here item.ready can render elements in the wrong order
            // refs.atIndex( index ) will force index and patch this issue 
            item.ready && <li ref={ itemRefs.atIndex(i) }>{item.name}</li>
        )}
    </ul>
}

Mounted and unmounted

Pretty self-explanatory, will be called when mounting or unmounting the component.

function MountUnmount ( props ) {
    const root = ref()
    
    mounted(() => {
        console.log("Component just mounted, refs are ready.", root.dom)
        // Can return an unmount function
        return () => {
            console.log("Will be called just before component unmount.", root.dom)
        }
    })
    
    unmounted( () => {
        console.log("Will be called just before component unmount.", root.dom)
    })
    
    return () => <div ref={ root }>...</div>
}

Changed

Changed factory hook is useful to detect changes into a component. With only one handler as argument, it will be called after each component render.

function ChangedComponent ( props ) {
    const root = ref()
    const number = state(0)
    changed(() => {
        // Called after each render
        // Ref and state are available
        console.log("Component updated", root.dom, number.value)
    })
    return () => <div ref={ root }>
        <button onClick={ number.set( number.value + 1) }>
            Update component</button>
    </div>
}

Changed can have a first argument to detect changes on values. Because we are in Factory phase, raw props or values can't be used directly. Note that the check function always returns an array.

function ChangedComponent ( props ) {
    const stateA = state()
    const stateB = state()
    changed(
        // The function detect changes only on stateA, stateB is ignored
        () => [stateA.value],
        // Called when change is detected
        () => {
            // StateA is updated
            console.log(stateA.value)
        }
    )
    return () => <div>...</div>
}

Return from the detect function can detect changes on multiple elements.

function ChangedComponent ( props ) {
    const stateA = state()
    const stateB = state()
    changed(
        // The function detect changes in stateA and props.name, stateB is ignored
        () => [stateA.value, props.name],
        // Called when change is detected in stateA OR props.name
        // Both new state and old state values are concatenated into arguments
        //      new array       |        old array      //
        (newStateA, newPropsName, oldStateA, oldPropsName) => {
            // Values array here are the last and previous returned array
            // Useful to detect changes, or pub-sub any other component or model
            console.log( newStateA, newPropsName, oldStateA, oldPropsName )
        }
    )
    return () => <div>...</div>
}

Changed handler has the same return behavior than mount and unmount.

function ChangedComponent ( props ) {
    const state = state()
    changed( () => [state.value], newValue => {
        // After change and at first render
        console.log("New value", newValue)
        return oldValue => {
            // Before change and before unmount
            console.log("Old value", oldValue)
        }
    })
    return () => <div>...</div>
}

More

Reflex is slim, but it still has some cool features for greater DX.

Automatic forwardRef

When attaching a ref from inside the component, an from the parent, it will just work as expected.

function Child () {
    // Works, will have component instance and div element
    const root = ref()
    return () => <div ref={ root }></div>
}
function Parent () {
    // Also works without forwardRef
    // will have component instance and div element
    const child = ref()
    return () => <div>
      <Child ref={ child } />
    </div>
}

Classes as array

Classes can be set as an array. Falsy values will be automatically filtered out.

function PureComponent ( props ) {
    const classes = [
        "PureComponent",
        props.modifier ? `PureComponent-${props.modifier}` : null,
        props.disabled && "disabled",
        ...props.classes
    ]
  return <div class={ classes }></div>
}
// Will have class="PureComponent PureComponent-big ParentComponent_pureComponent"
// Disabled is filtered out because props.disabled is not defined
const component = <PureComponent
  modifier="big"
  classes={["ParentComponent_pureComponent"]}
/>

About

History

Reflex idea comes from 2018 when React proposed React Hooks. After digging hooks for some months, a lot of people talked about the benefits of having a Factory Phase with a render function returned instead of all in one function. I proposed a proof of concept of a factory component system based on Preact. Years after using React hooks (with Preact a lot), I finally had time to get this idea working into a standalone lib ✨

Things missing

Here is the list of things missing from React :

  • React suspense (loading fallback)
  • React fiber (asynchronous rendering)
  • renderToString (for now only)
  • Class components
  • A lot of other stuff

Things missing from Solid :

  • Crazy performances
  • A lot of other stuff

Things missing from Preact :

  • Not so much I guess ?

Performances

Reflex goal is to be as performant as possible and as light as possible. Reflex will never be as performant than Solid (because of Virtual DOM), but will easily be more performant than React or Preact in a lot of cases.

Library weight will be around 4kb gzipped. It may be a bit more if we add some useful features. Not used features can be tree-shaken thanks to your bundler (like Parcel or Vite). See note about code golfing.

Demos

Click here to see some demo (WIP)

Roadmap

  • Better doc
  • Better demos
  • A solution for automatic forwardRef (store two refs into vnode ?)
  • Benchmarks with React / Preact / Solid
  • A npm create reflex-app script with Parcel
  • Smaller package / better performances

Unpkg

Reflex is available on Unpkg

You might also like...

A web video player built for the HTML5 world using React library.

video-react Video.React is a web video player built from the ground up for an HTML5 world using React library. Installation Install video-react and pe

Jan 6, 2023

A Discord bot library to make the development of a bot a little bit easier.

Discord Bot Framework (DBF) A basic Discord bot framework to allow for easier creation of bots. With this library, you can easily parse and handle com

Dec 23, 2021

Buzz, a Javascript HTML5 Audio library

Buzz is a small but powerful Javascript library that allows you to easily take advantage of the new HTML5 audio element. It tries to degrade silently on non-modern browsers.

Dec 10, 2022

A simple library for using the JavaScript Full Screen API.

BigScreen A simple library for using the JavaScript Fullscreen API. Why should I use it? BigScreen makes it easy to use full screen on your site or in

Dec 22, 2022

HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.

HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.

HLS.js is a JavaScript library that implements an HTTP Live Streaming client. It relies on HTML5 video and MediaSource Extensions for playback. It wor

Jan 2, 2023

A simple bot for twitch, which has a silly AI in places. The library "cleverbot-free" is used for AI.

A simple bot for twitch, which has a silly AI in places. The library

AITwitchChat A simple bot for twitch, which has a silly AI in places. The library "cleverbot-free" is used for AI. Requiments tmi.js Node.js 16.6.0 or

Dec 7, 2022

Full logging system using the djs library (v13.6.0)

Loggin-system-djs-v13.6 Full logging system using the djs library (v13.6.0) NOW ONTO MULTIPLE DISCLAIMERS: β—» It is crucial that you first understand t

Aug 26, 2022

🌈 A very simple window communication library

🌈 A very simple window communication library

window-channel A very simple window communication library. Get Start! Install npm install @haiyaotec/window-channel Usage/Examples Client import

Oct 29, 2022

Telegram-API Library Written in TypeScript

TeleLib a node js Telegram Wrapper written in TypeScript. installation yarn add @telelib/telelib or npm i --save @telelib/telelib how to use create a

Jul 27, 2022
Comments
  • Thoughts

    Thoughts

    Continuing discussion from this long thread : https://github.com/zouloux/prehook-proof-of-concept/issues/1

    Answering @xdev1 :

    @xdev1 : It would be really helpful, if the demos in reflex's README.md were written in TypeScript or at least a bunch of non-trivial examples.

    True ! In fact, if this lib is used someday, I'll create a cleaner doc with categories, Readme.md will become a classic lib homepage with basic facts and pointing to the doc. For now, I'll keep it this way because it's still in beta and only used by me πŸ₯Έ I'll just create some code sandboxes so new users could play with it in plain TSX without having to setup an env.

    @xdev1 : Also, using prettier would be very nice. Every syntax that is used with the reflex patterns should work fine with prettier, as a majority of developers (including me πŸ˜‰) use prettier wherever possible.

    Using prettier in the reflex/src ? Why not ! Again this is beta but I may accept to use prettier if I got others maintainers helping me. I'm not a huge fan on prettier on those kind of algorithmes, I often need manual formatting for better understanding. Also very opinionated but clearly not closed to prettier !

    @xdev1 : I would consider, not to use the name hooks anymore if it's not "react-like" hooks. Folks will get confused otherwise (especially those who do not like react-like hooks, I know this from own bad experience in other discussions πŸ˜„). Maybe extensions could be a good name instead, as those functions are conceptionally extension functions where the first argument (= object) will just be passed implicitly.

    Yep, I totally agree ! I was roaming into your https://github.com/js-works/preactive codebase recently and found the naming very interesting, I'll re-use this idea if you are OK πŸ™Œ You are right about how things are named, this is very important for global understanding.

    @xdev1 : One major goal for that factory-pattern based components should be to increase conciseness wherever possible and reasonable. In the old days we used stuff like this.props.label which was extremely cumbersome, React now uses just label which is awesome, while reflex uses props.label, which may look okayish first, but if you love React as I do and then you use reflex (or something similar), then even props.label seems way to noisy. I personally really prefer using the variable name p instead of props which reduces noise at a good amount especially in larger components. I know, normally we try to prevent single-character variables (except for maybe i, j, x, y etc.) but here this shortcut makes perfectly sense, IMHO. By the way, I'd also use s for a state object (i.e. s.count instead of state.count) and ctx for the used context values (i.e. ctx.theme).

    I agree about how concise is React but I do not find this too much of a drawback. Solid has the same way of dealing with reactive values and props (as in a, object, not destructurable), and it feel fine when you know it. Will keep plain props in source code and examples because not everybody knows that p means props, and by extensions componentProperties. Also always destructuring objects into vars can be sometime noisy, a lot of curly braces to understand, which is fine for a lot of people but can be messy if done wrong. I'll maybe find a way to make defaultProps work with destructuring somehow (in stateless), if it's possible it will be implemented but I feel that stateful and stateless components should not work too differently. If any stateless component you are writing is becoming stateful for any reason, you should not be forced to refacto too much the code, it can be source of errors and frustration.

    @xdev1 : Regarding stateless components: I would not provide some special API for those stateless components as it is not really necessary.

    Stateless components are / will be very much the same as React functional components, but with the defaultProps and shouldUpdate functions, working like factory components. Maybe it will be through the preset and shouldUpdate extensions, which will work both in factory and stateless components. This can be easily explained in the doc + by raising errors in dev mode if misused.

    @xdev1 : Frankly, I do not know, why count.set(count.value + 1) shall be better than setCount(getCount() + 1) or setCount(count() + 1) or count(count() + 1)

    Yep that's pretty much the same BUT : Now states are usable both ways :

    const myState = state( 0 )
    state.value ++ // will trigger component update
    // dom is not updated yet
    await state.set( newValue )
    // or
    await state.set( v => v + 1 )
    // dom is now updated and ready because of the await
    

    Also, having state.value as a getter, will allow us to implement atomic rendering (diff only nodes that use this getter). It will have better perfs than React in a lot of cases and useState cannot do it because of value as a variable (not possible to track usages) See @developit twitter thread on this : https://twitter.com/_developit/status/1549001036802625536

    @zouloux : I'll search for a solution but the preset hook is maybe the only way to have this working in strict mode.

    @xdev1 : The only type-safe solutions that I know are the following ...

    @zouloux : I'm starting to like the preset version ! The HOC (curried) version feels more wrong to me because there is a lot of curly / destructuring / generic noise which can be avoided. It is less chars but it feels more to me ^^

    }>({
      initialCount: 0
    })((p) => {
    

    As always, thanks for your time and your feedbacks ! You rock ! I'll try to re-read everything later because I'll get more subtleties. Also, I'm starting to use the lib on a personal project so I'll surely have some changes to do, will do better docs after this "alpha" phase is finished.

    opened by zouloux 2
Owner
Alexis Bouhet
Freelance Web Developer. Former Lead Dev @cher-ami @la-haute-societe. Web / Typescript / Node / React / Mobile / PHP
Alexis Bouhet
This is a simple web based media player for playing video and audio. Build with pure HTML, CSS and Javascript. No framework or library included.

Aim-Player This is a simple web based media player for playing video and audio. Build with pure HTML, CSS and Javascript. No framework or library incl

Aim Mikel 2 Jun 27, 2021
πŸ“· The fastest and most versatile JS EXIF reading library.

Usage β€’ Installation β€’ Quick start β€’ Demos β€’ API β€’ Perf β€’ Changelog β€’ FAQ β€’ Contributing ?? The fastest and most versatile JavaScript EXIF reading lib

Mike 786 Jan 2, 2023
:musical_score: ts-audio is an agnostic library that makes it easy to work with AudioContext and create audio playlists in the browser

ts-audio Β· ts-audio is an agnostic and easy-to-use library to work with the AudioContext API and create Playlists. Features Simple API that abstracts

Evandro Leopoldino Gonçalves 284 Dec 25, 2022
VexFlow 3 - A JavaScript library for rendering music notation and guitar tablature.

VexFlow 3 - A JavaScript library for rendering music notation and guitar tablature.

Mohit Cheppudira 3.5k Jan 6, 2023
A Node JS Express/Serverless demo application that creates a slideshow video using the Pexels image library and Shotstack video editing API.

Shotstack Pexels Slideshow Video Demo This project demonstrates how to use the Shotstack cloud video editing API to create a video using an HTML form

Shotstack 25 Dec 9, 2022
Aviatojs - A simple library to trim, cut and join audio files.

Aviatojs A simple library to trim, cut and join audio files. Usage For a fully working example refer to this example Importing import {AviatoAudio}

null 6 Oct 7, 2022
Library to calculate a Mean Opinion Score (MOS) from 1 to 5 for audio and video real time communications

RTC SCORE Library to calculate a Mean Opinion Score (MOS) from 1 to 5 for audio and video real time communications. The first version of the algorithm

Gustavo Garcia 25 Nov 27, 2022
Vio-MD is multi-device whatsapp bot using library @adiwajshing/baileys and example bot of Violetics API

Vio Multi Device WhatsApp Bot Use at your own risk! Build with Baileys and JavaScript's native Map class ( as a command handler ), using https://viole

Violetics 4 May 31, 2022
:loudspeaker: A JavaScript library to add voice commands to your sites, apps or games.

Voix JS A JavaScript library to add voice commands to your sites, apps or games. NOTE: At this time, this library is only compatible with Google Chrom

Guille Paz 548 Dec 8, 2022
AmplitudeJS: Open Source HTML5 Web Audio Library. Design your web audio player, the way you want. No dependencies required.

Documentation β€’ Examples β€’ Tutorials β€’ Support Us β€’ Get Professional Help AmplitudeJS is a lightweight JavaScript library that allows you to control t

Server Side Up 3.9k Jan 2, 2023