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 🙏

© 2026 – Pkg Stats / Ryan Hefner

classy-state

v1.1.5

Published

A package that allows classes to be use in useState and useContext

Readme

classy-state

Purpose

This library is created to leverage object orientated programming with react style hooks, using the best of both worlds. By providing new useStateClass and useContextClass functions.

Usage

Create a class that inherits from Action. This provides three new methods.

this.setState(), which is the same as React setState function.

this.use(), can be overriden to call React hooks in.

this.merge(newData), will merge the newData with the existing.

(the setState method is assigned in the custom useStateClass)

Create a class like you would in any other Object Orientated language with member variables and functions. The one key difference is updating state. Instead of changing variables locally call this.setState or this.merge to update variables. Then create this object in the (new) useStateClass or useStateClass functions.

import { createContext } from 'react';
import { Action, useStateClass, useStateClass } from "classy-state";

export class Counter extends Action<Counter> {
    public count: number = 0;

    public increment() { this.setCount(this.count + amount)) }
    public decrment() { this.setCount(this.count - amount) }
    private setCount(count: number) { this.merge({ count }) }
}


// use it as a simple state
function Component(){
    const counter = useStateClass(new Counter());
    return ( ... )
}


//use it in context
const CounterContext = createContext(new Counter());

function Component(){
    const counter = useStateClass(new Counter());
    return (
        <CounterContext.Provider value={counter}>
            <ChildComponent/>
        <CounterContext.Provider>
    )
}

function ChildComponent(){
    const counter = useContextClass(CounterContexts);
    return ( ... )
}

Examples

Counter class

export class Counter extends Action<Counter> {
    public count: number = 0;
    constructor(count: number) {
        super();
        this.count = count;
    }

    public increment() { this.add(1) }
    public decrement() { this.merge({ count: this.count - 1 }) }
    public add(amount: number) { this.setCount(this.count + amount) }
    public subtract(amount: number) { this.setCount(this.count - amount) }
    private setCount(count: number) { this.merge({ count }) }
}

Counter with useStateClass

import { useStateClass, Action } from "classy-state";
import {Counter} from '../Counter'

export default function CounterExample() {
    const counter = useStateClass(new Counter(5));

    return (
        <div>
            <h2>Counter Example</h2>
            <h3>{counter.count}</h3>
            <div>
                <button onClick={() => counter.increment()}>increment</button>
                <button onClick={() => counter.decrement()}>decrement</button>
            </div>
        </div>
    )
}

Counter Context

import { createContext } from "classy-state";
import { Counter } from '../Counter';

//provide global default value
const CounterContext = createContext(new Counter(5));

export default function CounterExample() {
    const counter = useStateClass(new Counter(5));
    return (
        //override global default value, note this is still a default
        <CounterContext.Provider value={counter}>
            <Header></Header>
            <Page></Page>
        </CounterContext.Provider>
    )
}

function Header() {
    const counter = useContextClass(CounterContext);
    return <div style={{ display: 'flex', flexDirection: 'column', alignItems: "center" }}>
        <h2>Counter Example</h2>
        <h3 style={{ textAlign: 'center' }}>{counter.count}</h3>
    </div>
}

function Page() {
    const counter = useContextClass(CounterContext);
    return <div style={{ display: 'flex', flexDirection: 'column', alignItems: "center" }}>
        <div>
            <button onClick={() => counter.increment()}>increment</button>
            <button onClick={() => counter.decrement()}>decrement</button>
        </div>
    </div>
}

Complex People List example

In this example, we create a list of people, with a name and DOB, this data is retrieved from the DB, is able to search for people by name, add a new person, finally edit a persons name.

Note the complexity of each different requirmenent and how it overlaps with state requirements of each other. We will heavily be utilising MixIn to make the design reusable.

Specifically ts-mixer library which is the best typescript mixin library I have found so far.

Main logic

Contains:

  • Person class - describes a person's name, DOB and changeName
  • People class - describes a list of people
  • PeopleSearch class - adds functionality for filtering out people based on searc keyword
  • People Remote class - pulls data from the database when first loaded
  • PeopleStateRemote class - the final state with all actions mixed in together

Structured like so:

  • PeopleStateRemote
    • PeopleSearch
      • Search
      • People
        • Person
    • PeopleRemote
      • IsLoading
      • People
        • Person

By using mixins we can do this re-inheritance of People, with both instances still refereing to the same data.

import { Action } from "classy-state";
import { Mixin } from 'ts-mixer';
import { useEffect } from "react";
import { Search } from "./Search";
import { isLoading } from "./Loading";

//A person with name, DOB and can change their name
export class Person extends Action {
    constructor(
        public name: string = "",
        public DOB: Date = new Date(Date.now())
    ) { super(); }

    setName(name: string) {
        console.log(name)
        this.setState({ ...this, name })
    }
}

//A list of people
export class People extends Action {
    public people: Person[] = [];

    init() {
        //have to assign each person their own setState function manually
        this.applySetStateToList(this.people);
    }

    setPeople(people: Person[]) {
        this.people = people;
        this.setState({ ...this })
    }

    addPerson(person: Person) {
        this.people.push(person);
        this.setState({ ...this })
    }
}


//make people searchable
export class PeopleSearch extends Mixin(People, Search) {
    public getPeople() {
        return this.itemsThatMatch(this.people, (person, name) => person.name === name);
    }
}

//pull people from a database
export class PeopleRemote extends Mixin(People, isLoading) {

    public init() {
        super.init();
        //hack to use hooks inside member functions
        const Foo = () => {
            useEffect(() => {
                setTimeout(() => {
                    this.setPeople([
                        new Person("Jason", new Date(Date.now())),
                        new Person("Maddy", new Date(Date.now()))
                    ]);
                    this.setLoading(false);
                }, 1000);
            }, [])
        };
        Foo();
    }
}



//mix all the actions together
export class PeopleStateRemote
    extends Mixin(PeopleSearch, PeopleRemote) { }

Reusable Logic

These classes can be reused easily with other parts of the system

  • Search - contains a keyword, and a helper function to filter items
  • isLoading - contains the state for loading
//reuasable search action
export class Search extends Action {
    public searchKey: string = "";

    public itemsThatMatch<T>(items: T[], predicate: (t: T, searchKey: string) => boolean) {
        if (this.searchKey)
            return items.filter((item) => predicate(item, this.searchKey));
        return items;
    }

    setSearch(search: string){
        this.setState({...this, searchKey: search})
    }
}

//reuasable loading action
export class isLoading extends Action {
    public isLoading: boolean = true;

    setLoading(loading:boolean){
        this.setState({...this, isLoading: loading})
    }
}

Rendering People

Rendering is split up into 4 components, and is structured hierarchialy as below

  • PeopleExample - the root component, which will hold all the state
    • Header - where a search bar will live
    • Page - where the list of People will be shown, and filters based on search keyword
      • PersonComponent - that renders a person name and DOB, and can edit the person name in a textfield

Note how all business logic implementation and requirements are not present in this file, only the state and the components and abstract actions.

import { createContext } from "react";
import { useStateClass, useContextClass} from "../../lib";
import { PeopleStateRemote, Person } from "../People";

const PeopleContext = createContext(new PeopleStateRemote());
const PersonContext = createContext(new Person());


export default function PeopleExample() {
    const people = useStateClass(new PeopleStateRemote());
    return (
        <PeopleContext.Provider value={people}>
            <Header></Header>
            <Page></Page>
        </PeopleContext.Provider>
    )
}

function Header() {
    const people = useContextClass(PeopleContext);
    return <div style={{ display: 'flex', flexDirection: 'column', alignItems: "center" }}>
        <h2>People Example</h2>
        <input type="text" value={people.searchKey} onChange={(e) => people.setSearch(e.target.value)} />
    </div>
}

function Page() {
    const people = useContextClass(PeopleContext);
    return <div style={{ display: 'flex', flexDirection: 'column', alignItems: "center" }}>
        { people.isLoading && "loading" }

        {people.getPeople().map((person) =>
            <PersonContext.Provider value={person}>
                <PersonComponent />
            </PersonContext.Provider>
        
        )}

        <button onClick={() => {
            const jason = new Person("Jason", new Date(Date.now()));
            people.addPerson(jason)
        }}>Add new person</button>

    </div>
}

function PersonComponent() {
    const person = useContextClass(PersonContext);
    return <div>
        <input type="text" value={person.name} onChange={(e) => {
            person.setName(e.target.value)
        }} />
        <p>{person.DOB.toString()}</p>
    </div>
}