Nostalgic Pickled Mango

    storeon-velo

    2.1.0 • Public • Published

    storeon-velo

    https://wix.com/velo corvid-storeon test status npm version minzip

    A tiny event-based state manager Storeon for Velo by Wix.

    state manager storeon-velo

    How to use

    You can install from Package Manager or use demo template.

    Example

    public/store.js

    import { createStoreon } from "storeon-velo";
    
    const counter = (store) => {
      store.on("@init", () => ({ count: 0 }));
      store.on("increment", ({ count }) => ({ count: count + 1 }));
    };
    
    export const { getState, setState, dispatch, connect, connectPage } = createStoreon([counter]);

    Page Code

    import { getState, setState, dispatch, connect, connectPage } from "public/store.js";
    
    // Subscribe for state property "count".
    // The callback function will be run when the page loads ($w.onReady())
    // and each time when property "count" would change.
    connect("count", ({ count }) => {
      $w("#text1").text = String(count);
    });
    
    // Wrapper around $w.onReady()
    // The callback function will be run once.
    connectPage((state) => {
      $w("#button1").onClick(() => {
        // Emit event
        dispatch("increment");
      });
    });

    Live Demo

    Install

    You use the Package Manager to manage the npm packages in your site.

    Check latest available version

    Package Manager panel in Wix editor, installing storeon-velo

    Wix Website Template

    Just open the template in the Wix Editor and play with storeon-velo

    API

    createStoreon

    Creates a store that holds the complete state tree of your app and returns 5 methods for work with the app state. (modules API)

    const { getState, setState, dispatch, connect, connectPage } = createStoreon(modules);

    Syntax

    function createStoreon(Array<Module | false>): Store
    
    type Store = {
      getState: Function
      setState: Function
      dispatch: Function
      connect: Function
      connectPage: Function
    }

    getState

    Returns an object that holds the complete state of your app.

    const state = getState();

    Syntax

    function getState(): object

    setState

    Set partial state. Accepts an object that will assign to the state.

    setState({ xyz: 123 });

    Syntax

    function setState(data: object): void

    dispatch

    Emits an event with optional data.

    dispatch("event/type", { xyz: 123 });

    Syntax

    function dispatch(event: string, data?: any): void

    connect

    Connects to state by property key. It will return the function disconnect from the store.

    const disconnect = connect("key", (state) => { });
    
    disconnect();

    You can connect for multiple keys, the last argument must be a function.

    connect("key1", "key2", (state) => { });

    Syntax

    function connect(...args: [key: string, ...key: string[], handler: ConnectHandler]): Disconnect
    
    type ConnectHandler = (state: object) => void | Promise<void>
    
    type Disconnect = () => void

    connectPage

    Sets the function that runs when all the page elements have finished loading. (wrapper around $w.onReady())

    connectPage((state) => { });

    Syntax

    function connectPage(initFunction: ReadyHandler): void
    
    type ReadyHandler = (state: object) => void | Promise<void>

    Store

    The store should be created with createStoreon() function. It accepts a list of the modules.

    Each module is just a function, which will accept a store and bind their event listeners.

    import wixWindow from "wix-window";
    import { createStoreon } from "storeon-velo";
    
    // Business logic
    const appModule = (store) => {
      store.on("@init", () => {
        return {
          items: [],
        };
      });
    
      store.on("items/add", ({ items }, item) => {
        return {
          items: [...items, item],
        };
      });
    };
    
    // Devtools
    const logger = (store) => {
      store.on("@dispatch", (state, [event, data]) => {
        if (event === "@changed") {
          const keys = Object.keys(data).join(", ");
          console.log("changed:", keys, state);
        } else if (typeof data !== "undefined") {
          console.log("action:", event, data);
        } else {
          console.log("action:", event);
        }
      });
    };
    
    export const { getState, setState, dispatch, connect, connectPage } = createStoreon([
      appModule,
      wixWindow.viewMode === "Preview" && logger,
    ]);

    Syntax

    function createStoreon(Array<Module | false>): Store
    
    type Module = (store: StoreonStore) => void
    
    type StoreonStore = {
      dispatch: Function
      on: Function
      get: Function
      set: Function
    }

    Storeon store methods

    store.dispatch

    Emits an event with optional data.

    store.dispatch("event/type", { xyz: "abc" });

    Syntax

    function dispatch(event: string, data?: any): void

    store.on

    Adds an event listener. store.on() returns cleanup function. This function will remove the event listener.

    const off = store.on("event/type", (state, data) => { });
    
    off();

    Syntax

    function on(event: string, listener: EventListener): Unbind
    
    type EventListener = (state: object, data?: any) => Result
    
    type Unbind = () => void
    
    type Result = object | void | Promise<void> | false

    store.get

    Returns an object that holds the complete state of your app. The app state is always an object.

    const state = store.get();

    Syntax

    function get(): object

    store.set

    Set partial state. Accepts an object that will assign to the state. it can be useful for async event listeners.

    store.set({ xyz: 123 });

    Syntax

    function set(data: object): void

    Events

    There are 5 built-in events:

    @init

    It will be fired in createStoreon(). The best moment to set an initial state.

    store.on("@init", () => { });

    @ready

    It will be fired in $w.onReady() when all the page elements have finished loading.

    store.on("@ready", (state) => { });

    @dispatch

    It will be fired on every new action (on dispatch() calls and @changed event). It receives an array with the event name and the event’s data. it can be useful for debugging.

    store.on("@dispatch", (state, [event, data]) => { });

    @set

    It will be fired when you use setState() or store.set() calls.

    store.on("@set", (state, changes) => { });

    @changed

    It will be fired when any event changes the state. It receives object with state changes.

    store.on("@changed", (state, changes) => { });

    You can dispatch any other events. Just do not start event names with @.

    Reducers

    If the event listener returns an object, this object will update the state. You do not need to return the whole state, return an object with changed keys.

    // "products": [] will be added to state on initialization
    store.on("@init", () => {
      return { products: [] };
    });

    Event listener accepts the current state as a first argument and optional event object as a second.

    So event listeners can be a reducer as well. As in Redux’s reducers, you should change immutable.

    Reducer

    store.on("products/add", ({ products }, product) => {
      return {
        products: [...products, product],
      };
    });

    Dispatch

    $w("#buttonAdd").onClick(() => {
      dispatch("products/add", {
        _id: uuid(),
        name: $w("#inputName").value,
      });
    });

    Connector

    connect("products", ({ products }) => {
      // Set new items to repeater
      $w("#repeater").data = products;
      // Update repeater items
      $w("#repeater").forEachItem(($item, itemData) => {
        $item("#text").text = itemData.name;
      });
    });

    Async operations

    You can dispatch other events in event listeners. It can be useful for async operations.

    Also, you can use store.set() method for async listeners.

    import wixData from "wix-data";
    import { createStoreon } from "storeon-velo";
    
    const appModule = (store) => {
      store.on("@init", () => {
        return {
          products: [],
          error: null,
        };
      });
    
      store.on("@ready", async () => {
        try {
          // wait to fetch items from the database
          const { items } = await wixData.query("Products").find();
    
          // resolve
          store.set({ products: items });
        } catch (error) {
          // reject
          store.set({ error });
        }
      });
    
      // Listener with the logic of adding new items to list
      store.on("products/add", ({ products }, product) => {
        return {
          products: [product, ...products],
        };
      });
    
      store.on("products/save", async (_, product) => {
        try {
          // wait until saving to database
          await wixData.save("Products",  product);
    
          // resolve
          store.dispatch("products/add", product);
        } catch (error) {
          // reject
          store.set({ error });
        }
      });
    }
    
    const { getState, setState, dispatch, connect, connectPage } = createStoreon([
      appModule,
    ]);

    Work with Repeater

    Use forEachItem() for updating a $w.Repeater items into connect() callback.

    connect("products", ({ products }) => {
      // Set new items to repeater
      $w("#repeater").data = products;
      // Update repeater items
      $w("#repeater").forEachItem(($item, itemData) => {
        $item("#text").text = itemData.name;
      });
    });

    Never nest the event handler for repeated items into any repeater loop.

    Use global selector $w() instead and use context for retrieving repeater item data.

    connect("products", ({ products }) => {
      $w("#repeater").data = products;
    
      $w("#repeater").forEachItem(($item, itemData) => {
        $item("#text").text = itemData.name;
    
    -   $item("#repeatedContainer").onClick((event) => {
    -     dispatch("cart/add", itemData);
    -   });
      });
    });
    
    + connectPage(() => {
    +   $w("#repeatedContainer").onClick((event) => {
    +     const data = $w("#repeater").data;
    +     const itemData = data.find(item => item._id === event.context.itemId);
    +
    +     dispatch("cart/add", itemData);
    +   });
    + });

    more:

    License

    MIT

    Install

    npm i storeon-velo

    DownloadsWeekly Downloads

    8

    Version

    2.1.0

    License

    MIT

    Unpacked Size

    21.3 kB

    Total Files

    6

    Last publish

    Collaborators

    • shoonia