npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@runopencode/stencil-state-store

v1.0.5

Published

Light state store management for Stencil

Downloads

2

Readme

Built With Stencil

Stencil state store

This is simplified version of state store, adjusted to the needs for the composition of several components that share and update common state.

Although there are other approaches, like property probing, or state tunneling, this approach is inspired by NGXS and based on RXJS.

While NGXS have concept of actions which is dispatched and accepted by store, in this simplified version, components have direct access to store and can invoke patch and set methods to mutate state.

Rationale behind this is that web components should be simple and communication lightweight. If something like NGXS is required to manage components communication, you should to reconsider using some application framework and/or more complex library for state management.

Web components should be used for building web components. For complex use case scenarios, perhaps application frameworks are more suitable.

Learn by example

Let's say that we have some state that needs to be shared among components:

export interface CarouselState {
    page: number;
} 

while our composition of components is:

<runopencode-carousel>
    
    <runopencode-carousel-stage>
        <img src="example-1.jpg"/>
        <img src="example-2.jpg"/>
        <img src="example-3.jpg"/>
    </runopencode-carousel-stage>
    
    <runopencode-carousel-pager>
    </runopencode-carousel-pager>
    
</runopencode-carousel>

Component runopencode-carousel-stage is in charge to render and slide those images, while runopencode-carousel-pager is in charge to display current page. Since those components are siblings, they can not use property probing approach. Component runopencode-carousel could provide for them a shared state store, that is, an instance of StoreInterface containing instance of CarouselState.

import {Component, ComponentInterface, Host, h}      from "@stencil/core";
import {Provide, Store}                              from "@runopencode/stencil-state-store";
import {CarouselState}                               from "./state";

@Component({
    tag:    'runopencode-carousel',
    shadow: true,
})
export class Carousel implements ComponentInterface {
    
        @Provide({
            name:     'runopencode-carousel-store',
            defaults: {
                page: 1
            }
        })
        private store: Store<CarouselState>;
        
        public render() {
            return (
                <Host>
                    <state-store-provider provider={this}>               
                        <slot/>
                    </state-store-provider>
                </Host>
            );
        }
}

Note that it is even possible not to wrap content within <state-store-provider> tag, you may just provide a component as direct child of <Host> within render() method. Example:

public render() {
    return (
        <Host>
            <state-store-provider provider={this}/>
            <slot/>
        </Host>
    );
}

Component runopencode-carousel-pager can consume that state store:

import {Component, ComponentInterface, Host, h}      from "@stencil/core";
import {Provide, Store}                              from "@runopencode/stencil-state-store";
import {CarouselState}                               from "./state";
import {Unsubscribable}                              from "rxjs";

@Component({
    tag:    'runopencode-carousel-pager',
    shadow: true,
})
export class CarouselPager {
    
    @Consume('runopencode-carousel-store')
    private store: Promise<Store<CarouselState>>;

    @State()
    private page: number;
        
    private subscription: Unsubscribable;
    
    public componentDidLoad(): void {
        let store: Store<CarouselState> = await this.store;
        this.subscription = store.subscribe((state: CarouselState) => {
            this.page = state.page;
        });
    }
    
    public disconnectedCallback(): void {
        this.subscription.unsubscribe();
    }
    
    public render() {
        return (
            <Host>
            <state-store-consumer consumer={this}/>
            <div>
                Current slide is {this.page}
            </div>           
            </Host>
        );
    }
}

So, there are few classes, decorators and interfaces involved here.

  1. You need to define your state interface. It is simple key, value pair, defined by following:
export interface CarouselState {
    page: number;
} 
  1. You have to provide your state store, with default values and unique name from parent component:
@Provide({
    name:     'runopencode-carousel-store',
    defaults: {
        page: 1
    }
})
public store: Store<CarouselState>;

Note that defaults may be a function which creates new default state.

That component must use state-store-provider component, with provider property referencing to this when rendering component.

public render() {
    return (
        <Host>
            <state-store-provider provider={this}/>
            <slot/>
        </Host>
    );
}
  1. You have to consume your state
@Consume('runopencode-carousel-store')
private store: Promise<Store<CarouselState>>;
  1. Trough subscription you can follow changes of state and update your component state:
public componentDidLoad(): void {
    let store: Store<CarouselState> = await this.store;
    this.subscription = store.subscribe((state: CarouselState) => {
        this.page = state.page;
    });
}    

IMPORTANT NOTES:

  • Consuming component (consumer) should subscribe for state change in componentDidLoad lifecycle method. This is stenciljs limitation, component must be rendered in order to require for shared state.
  • You might notice that Promise will be provided to consumer, not the state. There is no guarantee in component rendering order (event though there is clear parent-child relation), therefore, providing operation is async.
  • If Promise for store in consuming component is returned in componentWillLoad or componentWillRender lifecycle methods, child component will never be rendered, have that in mind.

State store implementation.

Do note that instance of your state store implements Store interface defined as follows:

import {Observable, PartialObserver, Subscribable, Subscription} from "rxjs";

export interface Store<T> extends Subscribable<T> {

    /**
     * Get observable.
     */
    observer: Observable<T>;

    /**
     * Select slice of state.
     */
    select(selector: (state: T | null) => void): Observable<T>;

    /**
     * Get current state.
     */
    snapshot(): T | null;

    /**
     * Set state.
     */
    set(state: T): void;

    /**
     * Patch state.
     */
    patch(state: Partial<T>): void;

    /**
     * Notify observers about error.
     */
    error(err: any): void;

    /**
     * Subscribe to state change.
     */
    subscribe(next?: PartialObserver<T> | ((value: T) => void)): Subscription;
}
    

See demo on YouTube: https://youtu.be/D07vAxlEUS0.

[ TODO ]

  • Write tests
  • Improvements with custom decorators - if custom class decorators become supported, implementation could be improved.