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

hyperappish

v1.6.0

Published

A minimal, hyperapp-like, wired action, state handling thingy that works with plain react components

Downloads

15

Readme

hyperappish

npm Build Status

A minimal, zero dependency (!), hyperapp-like, wired action, state handling-thingy that works with plain react components.

npm install hyperappish

How does it work?

Create a state tree and a corresponding operations object that modify each part of it.

const state = {
  counter: {
    n: 42
  }
};

const ops = {
  counter: {
    increment: state => ({ n: state.n + 1 })
  }
};

Actions are automatically bound to the part of the state that matches the key under which they are defined in the operations object (much like in hyperapp). They are called with this part of the state automatically when invoked.

E.g. the increment action will get passed the counter part of the state when it is invoked, as it resides under the counter key of the ops object.

Call mount with the state and operations to connect them.

import { mount } from "hyperappish";
const { run, actions } = mount(state, ops);

Call run with a function to render your application. This function is passed the state every time it is changed.

import React from "react";
import { render } from "react-dom";

const el = document.querySelector(".app");
run(state =>
  render(<button onClick={ () => actions.counter.increment() }>
           {state.counter}++
         </button>, el));

This renders a button with the value 42++ that when clicked will increment its value, over and over, ad infinitum.

Run it or play with it in codesandbox.

Or head over to torgeir/hyperappish-example for a complete example.

Promises, observables and middleware

This larger, contrieved example shows how to

  • Compose promises in actions
  • Return state directly from actions, even from promises
  • Express advanced async flows declaratively with e.g. observables from rxjs
  • Use middlewares to extend the default behavior, e.g. for logging actions or state after each change
import { mount } from "hyperappish";
import React from "react";
import ReactDOM from "react-dom";
import Rx from "rxjs/Rx";

const wait = s => new Promise(resolve => setTimeout(resolve, s * 1000));

const state = {
  incrementer: {
    incrementing: false,
    n: 0
  },
  selection: {
    user: null
  },
  users: {
    list: []
  }
};

const ops = {
  incrementer: {
    start: state => ({ ...state, incrementing: true }),
    increment: state => ({ ...state, n: state.n + 1 }),
    stop: state => ({ ...state, incrementing: false })
  },
  selection: {
    select: user => ({ user }),
    remove: () => ({ user: null })
  },
  users: {
    list: state =>
      wait(2)
        .then(_ => fetch("https://jsonplaceholder.typicode.com/users"))
        .then(res => res.json())
        .then(users => users.map(({ id, name }) => ({ id, name })))
        .then(list => ({ ...state, list }))
  }
};

const { run, actions, middleware, getState } = mount(state, ops);

const SelectedUser = ({ user }) => (
  <div>
    <h2>Selected user</h2>
    <span>
      {user.name} <button onClick={() => actions.selection.remove()}>x</button>
    </span>
  </div>
);

const User = ({ user, onClick = v => v }) => (
  <span onClick={onClick} style={{ cursor: "pointer" }}>
    {user.name} ({user.id})
  </span>
);

const Users = ({ list }) => {
  if (!list.length) {
    actions.users.list();
    return <span>Loading..</span>;
  }

  return (
    <div>
      <h2>Users</h2>
      {list.map(user => (
        <div key={user.id}>
          <User user={user} onClick={() => actions.selection.select(user)} />
        </div>
      ))}
    </div>
  );
};

const Incrementer = ({ n }) => (
  <div>
    <h2>Incrementer</h2>
    Incrementing: {n}
    <button onClick={() => actions.incrementer.start()}>start</button>
    <button onClick={() => actions.incrementer.stop()}>stop</button>
  </div>
);

const App = ({ selection, users, counter, incrementer }) => (
  <div>
    {selection.user && <SelectedUser user={selection.user} />}
    <Users {...users} />
    <Incrementer {...incrementer} />
  </div>
);

const incrementer = action$ =>
  action$
    .filter(action => action.type == "incrementer.start")
    .switchMap(() =>
      Rx.Observable.interval(100).takeUntil(
        action$.filter(action => action.type == "incrementer.stop")
      )
    )
    .map(() => actions.incrementer.increment());

const el = document.querySelector(".app");
run(state => ReactDOM.render(<App {...state} />, el), [
  middlewares.logActions,
  middleware.callAction
  middlewares.promise,
  middlewares.observable(incrementer),
  middlewares.logState(getState)
]);

Run it or play with it in codesandbox.

Middlewares

If you choose to provide your own middlewares, remember to add middleware.callAction (returned from mount) early in middleware list. This is the middleware that hyperappish use to actually call the action function with its corresponding state before returning control to the other middlewares.

You can override the default middleware by passing a list of middlewares as the second parameter to run(). These receive each action and the next middleware in the list, and may choose how to proceed.

Here are a couple of useful ones:

const middlewares = {
  promise: (action, next) =>
    typeof action.result.then == "function"
      ? action.result.then(result => next({ ...action, result }))
      : next(action),

  observable: (...epics) => {
    const action$ = new Rx.Subject();
    epics.map(epic => epic(action$).subscribe(v => v));
    return (action, next) => {
      const result = next(action);
      action$.next(action);
      return result;
    };
  },

  logActions: (action, next) => (console.log("action", action), next(action)),

  logState: getState => (action, next) => {
    const res = next(action);
    console.log("state", getState()));
    return res;
  }
};

Contributions?

Most welcome! Hit me up with a PR or an issue.