Reka State Management

website·Indexed 29 days ago

https://reka.js.org/

An experimental state management system designed for building no-code editors and drag-and-drop page builders. Reka provides a Program AST, a parser for creating AST nodes, and multiplayer capabilities via the @rekajs/collaboration package using Yjs CRDTs. It includes specific integrations for React through the RekaProvider and supports the definition of custom data types and extensions.

Tokens
10K
Snippets
51
Records
59
Agent score
48%

What's inside Reka

  1. Overview of Reka Collaboration and CRDTs

    Reka's core State is not a CRDT by design to maintain portability. To enable real-time multiplayer capabilities, the @rekajs/collaboration package provides an extension that mirrors the Reka State into a Yjs CRDT. Changes in the State are propagated to the mirrored CRDT and synced across all clients, then applied back to each client's core State structure.

    Note that while Reka State is a nested tree, it is represented as a flat tree within the Yjs CRDT to facilitate synchronization.

  2. Overview of Reka state management

    Reka is a state management system specifically designed for building no-code editors. It uses an Abstract Syntax Tree (AST) powered state system that allows end-users to create complex UI components with stateful values and templating capabilities (such as conditional rendering and expressions). It is primarily used to power Craft.js and its page builders.
  3. Understand Reka's role in Craft.js state management

    Reka is an experimental state management system designed to replace the internal state management of Craft.js. While Craft.js provides high-level page builder functionalities like drag-and-drop, Reka aims to extend the capabilities of the EditorState (the implicit tree data structure representing the page).

    Unlike the original Craft.js state, which primarily allows for reordering elements and mutating static prop values, Reka is intended to enable end-users to build UI components with the same flexibility as a developer writing React code, including:

    • Using JavaScript expressions
    • Holding stateful values
    • Conditional rendering based on expressions
    • Iterating through arrays to render multiple elements
  4. Understand the role of Frame and View in Reka.js

    In Reka.js, a Frame is an instance of a component defined in the State. Its primary purpose is to output a View, which is a serializable JSON structure representing the static output of a component. The View is recomputed whenever the component's definition in the state is updated or a side effect (such as mutating a global variable) occurs.
    // Example of a View output by a Frame for a Button component
    {
      "type": "TagView",
      "tag": "button",
      "props": {
        "className": "bg-blue-900"
      },
      "children": [
        {
          "type": "TagView",
          "tag": "text",
          "props": { "value": "I'm enabled!" }
        }
      ]
    }
  5. Reference other components within a template

    To nest components within a TagTemplate, use the ComponentTemplate type. This allows a RekaComponent to reference another component by its identifier.
    {
      type: "TagTemplate",
      name: "div",
      children: [
        {
          type: "ComponentTemplate",
          component: { type: "Identifier", name: "Button" }, // References another RekaComponent named 'Button'
          props: {},
          children: []
        }
      ]
    }
  6. Install Reka core and types

    To use Reka, you need to install two packages: @rekajs/types for creating Reka data types (State AST nodes) and @rekajs/core for creating Reka instances.
    npm install @rekajs/types @rekajs/core
  7. Implement dynamic logic in RekaComponent templates

    The template property of a RekaComponent supports dynamic expressions for props, conditional rendering, and list iteration using TagTemplate.
    // 1. Dynamic Props using Identifiers
    {
      type: "TagTemplate",
      name: "div",
      props: {
        className: { type: "Identifier", name: "color" }
      },
      children: []
    }
    
    // 2. Conditional Rendering using the 'if' property
    {
      type: "TagTemplate",
      name: "div",
      props: {},
      children: [],
      if: {
        type: "BinaryExpression",
        left: "counter",
        operator: "==",
        right: 0
      }
    }
    
    // 3. List Rendering using the 'each' property
    {
      type: "TagTemplate",
      name: "div",
      props: { className: { type: "Identifier", name: "color" } },
      children: [],
      each: {
        iterator: { type: "Identifier", name: "someArrayValue" },
        alias: "color",
        index: "i"
      }
    }
  8. Define and use External Components in Reka

    External Components allow you to expose predefined React components to end-users so they can be referenced within a RekaComponent template. These components exist outside of the State and are registered during the Reka.create initialization using t.externalComponent.
    const Icon = ({ name, size }) => {
      return React.createElement(icons[name], { size });
    };
    
    const reka = Reka.create({
      externals: {
        components: [
          t.externalComponent({
            name: 'Icon',
            render: (props) => <Icon {...props} />
          })
        ]
      }
    });
    
    // Reference the external component in the state/AST:
    {
      type: "RekaComponent",
      template: {
        type: "TagTemplate",
        name: "div",
        children: [
          {
            type: "ComponentTemplate",
            component: { type: "Identifier", name: "Icon", external: true },
            props: { size: "large" },
            children: []
          }
        ]
      }
    }
    
  9. Create a new Reka Type

    Create a new type using the provided builder functions (which return a Type instance) or by manually instantiating the class.
    import * as t from '@rekajs/types';
    
    // Using the builder function
    const literal = t.literal({
      value: 0
    });
    
    // Or manually
    const manualLiteral = new t.Literal({ value: 0 });
  10. Initialize Yjs document with a local default state

    Because a new Yjs document is empty, you may need to pre-generate an encoded state update to set an initial state locally. This involves two steps: creating a script to serialize a Reka state into a Yjs update, and applying that update in the application before initializing the CollabExtension.
    // --- STEP 1: Generate the encoded update (Run as a script) ---
    import { jsToYType } from '@rekajs/collaboration';
    import { Reka } from '@rekajs/core';
    import * as t from '@rekajs/types';
    import * as Y from 'yjs';
    import fs from 'fs';
    
    const doc = new Y.Doc();
    const type = doc.getMap('my-collaborative-editor');
    const reka = Reka.create();
    
    reka.load(t.state({
      program: t.program({
        components: [t.rekaComponent(...) ]
      }),
    }));
    
    const flattenState = t.flatten(reka.state);
    const { converted } = jsToYType(flattenState);
    
    type.set('document', converted);
    const update = Y.encodeStateAsUpdate(doc);
    const encoded = Buffer.from(update).toString('base64');
    
    fs.writeFileSync('./generated/encoded-initial-update.ts', `export const ENCODED_INITIAL_STATE = '${encoded}';`);
    
    // --- STEP 2: Apply the update in your app ---
    import { ENCODED_INITIAL_STATE } from './generated/encoded-initial-state';
    
    const doc = new Y.Doc();
    const type = doc.getMap('my-collaborative-editor');
    
    // Apply the pre-generated state before creating the extension
    Y.applyUpdate(doc, Buffer.from(ENCODED_INITIAL_STATE, 'base64'));
    
    const CollabExtension = createCollabExtension(type);
    const reka = Reka.create({ extensions: [CollabExtension] });
    // ... load state and bind provider as usual
  11. Implement real-time collaboration with Yjs

    Reka supports multiplayer collaboration via the @rekajs/collaboration package, which provides a CRDT-backed extension using Yjs.
    import { Reka } from '@rekajs/core';
    import * as t from '@rekajs/types';
    import { createCollabExtension } from '@rekajs/collaboration';
    import * as Y from 'yjs';
    import { WebrtcProvider } from 'y-webrtc';
    
    const doc = new Y.Doc();
    const type = doc.getMap('my-collaborative-editor');
    
    const reka = Reka.create({
      extensions: [createCollabExtension(type)],
    });
    
    reka.load(t.unflatten(type.getMap('document')));
    new WebrtcProvider('collab-room', doc);
  12. Subscribe to State or View changes

    Use reka.watch() to execute a callback whenever a Reka data structure (such as State or View) changes. You can use t.assert() to ensure a node is of a specific type before accessing its properties.
    // Watch for changes in a component template
    reka.watch(() => {
      if (appComponent.template instanceof t.TagTemplate) {
        console.log('appComponent =>', appComponent.template.tag);
      }
    });
    
    // Watch for changes in a resulting View
    reka.watch(() => {
      if (frame.view) {
        console.log(
          'frame root tag =>',
          t.assert(frame.view.render[0], t.TagView).tag
        );
      }
    });
    
    // Trigger the watch via a change
    reka.change(() => {
      t.assert(appComponent.template, t.TagTemplate).tag = 'section';
    });