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

organic-lit

v1.0.1

Published

A library to share messages in your lit-element organism without additional wiring. See the tabs for some examples

Downloads

6

Readme

Organic Lit

A library to share messages in your lit-element organism without additional wiring. See the tabs for some examples

Note

This library is an experimental approach to state management, and is mostly me "thinking out loud". If you want to use it (or something like it) feel free, just be aware that I feel it's partly too implicit, needs to much thinking to do it right, and I can think of a dozen ways it would screw you over.

That being said, have fun.

Purpose

The most difficult part of every application is state management. There are sufficient solutions for local state (the element itself), for the application it's more difficult.

Properties/Events can help you there, but might lead to prop-drilling (passing properties down until they are needed), or via context in libraries like react which is tightly coupled to the UI itself.

This library uses an approach that focused on the global (organism) level, allowing you to send messages between cells (elements) of your page.

Wording/Domain

This library uses the wording that is coming from Biology, and at such aims to replicate patterns learned there with some abstraction on top of it.

| Name | Description | | ---------------- | ----------------------------------------------------------------------------------------------------------------------- | | Organism | The application, or group of applications (microfrontends) that act as one | | Hormone | A specific message, send by special hormone producing entities that can released and carry some state | | Receptor | An interface on a LitElement that can receive one type of hormone and gets triggered every time the hormone is released | | Hypothalamus | Allows to orchestrate hormones and trigger side-effects on a global (organism wide) level |

The idea that organic-lit follows is different from other libraries because it means that hormones must not be triggered by components only, but basically by everything, and therefore allows you to, as an example, connect a web-socket service class with your components.

In difference to libraries like redux it means that you don't have one global state, but a state for each hormone that is orchestrated by events.

Usage

Hormones and receptors

The hormone is the base unit, and has to be defined first anywhere.

The defined hormone can then be used to be released, or to define a receptor.

const hormone = defineHormone("example", false);

pureLit("some-element", (element) => {
  // define receptor
  const receptorState = useReceptor(element, hormone);
  return html`<p>Receptor State: ${receptorState}</p>`;
});

// some-element: <p>Receptor State: false</p>

// release hormone
releaseHormone(hormone);

// some-element: <p>Receptor State: true</p>

Unlike the hormone you can find in nature, this one however can transport additional information if you need it. Let's take the example, but this time with a counter

const hormone = defineHormone("example", { count: 0 });

pureLit("some-element", (element) => {
  // define receptor
  const { count } = useReceptor(element, hormone);
  return html`<p>Receptor State: ${count}</p>`;
});

// some-element: <p>Receptor State: 0</p>

// release hormone
releaseHormone(hormone, (currentCount) => ({
  count: currentCount + 1,
}));

// some-element: <p>Receptor State: 1</p>

In theory, you can also release the hormone from the same component, but really you can release it everywhere as they are global in the organism.

Receptors can also have filters. This can be used to manage the global state drawbacks. Let's take the counter example, and a page where two counters are used.

const hormone = defineHormone("example", { count: 0, counter: undefined });

pureLit("some-element", (element) => {
  const { count } = useReceptor(
    element,
    hormone,
    // the filter that only applies the counter if the name===counter
    ({ counter }) => counter === element.name
  );
  return html`<p>Receptor State: ${count}</p>`;
});

// some-element[name=first]: <p>Receptor State: 0</p>
// some-element[name=other]: <p>Receptor State: 0</p>

// release hormone
releaseHormone(hormone, (currentCount) => ({
  count: currentCount + 1,
  counter: "first",
}));

// some-element[name=first]: <p>Receptor State: 1</p>
// some-element[name=other]: <p>Receptor State: 0</p>

If we wouldn't have added the filter, all counter instances would have been incremented. organic-lit also takes care that only the one element that receives the change is re-rendered, the other one remains untouched.

While this might look like a drawback at first (and it is something that can lead to bugs if not tested properly), it is actually one of the huge advantages of organic-lit. Think about a spreadsheet like application. We have thousands of identical cells, some of those with references to others. If a field changes, it releases a hormone, and the others can check if they are affected and change only if.

const cellChanged = defineHormone("cell/changed", {
  row: 0,
  column: "A",
  value: "",
});

pureLit("cell-element", (element) => {
  const { row, cell, value } = element;
  useReceptor(
    element,
    cellChanged,
    // the filter that only applies if the field has a formula and references the field that changed
    (cell) => isFormula(value) && hasReference(value, cell)
  );
  return isFormula(value)
    ? html`${calculatedField(value)}`
    : html`<input
        type="text"
        @change=${({ target }) =>
          releaseHormone(cellChanged, {
            value: target.value,
            row,
            cell,
          })}
        value=${value}
      />`;
});

Hypothalamus

As your application grows, you will get to the point where you need some orchestration. For that you can use the hypothalamus, which allows you to trigger side-effects on released hormones.

Let's take a look at an example todo-list

const todoAdd = defineHormone<string>("todo/add");
const todoList = defineHormone<string[]>("todo/list", { defaultValue: [] });

hypothalamus.on(todoAdd, (todo) =>
  releaseHormone(todoList, (todos) => [...todos, todo])
);

pureLit("component-list", (element) => {
  const list = useReceptor(element, element.receptor);
  return html`<ul>
    ${list.map((item) => html`<li>${item}</li>`)}
  </ul>`;
});

export default pureLit("todo-app", () => {
  return html`
    <input-with-button @onSubmit=${(value) => releaseHormone(todoAdd, value)}>
      Add todo
    </input-with-button>
    <h2>Your todos</h2>
    <div><component-list .receptor=${todoList}></component-list></div>
  `;
});

The two hormones are separating the information of the change, and the state of the list. When we release the todoAdd Hormone, the hypothalamus releases the todoList, which will be received by the component-list that uses the receptor.

The hypothalamus allows you to collect multiple hormones, and trigger only after all were released.

const corticoliberin = defineHormone("Corticoliberin", { defaultValue: false });
const adrenocorticotropin = defineHormone("Adrenocorticotropin", {
  defaultValue: false,
});

const receiver = jest.fn();

hypothalamus.on([corticoliberin, adrenocorticotropin], (result) => {
  receiver(result);
});

it("does not trigger when only no hormone is released", () => {
  expect(receiver).not.toBeCalled();
});

it("does not trigger when only one hormone is released", () => {
  releaseHormone(corticoliberin);
  expect(receiver).not.toBeCalled();
});

it("triggers the connection when all hormones are released", () => {
  releaseHormone(adrenocorticotropin);
  releaseHormone(corticoliberin);
  expect(receiver).toBeCalled();
});

it("doesn't trigger the connection again after all hormones are released but only after all hormones are released a second time", () => {
  releaseHormone(adrenocorticotropin, true);
  releaseHormone(corticoliberin, true);
  expect(receiver).toBeCalledTimes(1);
  // check the result directly
  expect(receiver).toBeCalledWith({
    Adrenocorticotropin: true,
    Corticoliberin: true,
  });
  // or use the getValue helper function that allows passing the hormone
  const result = receiver.mock.calls[0][0];
  expect(getValue(adrenocorticotropin, result)).toBe(true);
  expect(getValue(corticoliberin, result)).toBe(false);

  releaseHormone(corticoliberin);
  expect(receiver).toBeCalledTimes(1);
  releaseHormone(adrenocorticotropin);
  expect(receiver).toBeCalledTimes(2);
});

Read Once Hormone

Read Once Hormone are a special types of hormone that reset their state to the initial value after all receivers have received the new value. An example can be a form that changes it's value to true once submitted, and back to false immediately after, or the spreadsheet example where every hormone is a Read Once Hormone.

const doSubmit = defineReadOnceHormone("submit");
const allValidators = ["firstName", "lastName"]
    .map(field => defineHormone(`validate/${field}`, {
        field,
        isValid: false,
    });
const validationFailed = defineReadOnceHormone("validationFailed")
const submitDone = defineReadOnceHormone("submitted")

pureLit("input-element", (el) => {
  const submitting = useReceptor(el, doSubmit);
  // this will be true only the first time this hormone is released
  if (submitting) {
    releaseHormone({ name: `validate/${field}` },
        { field: el.name, isValid: validate(el) });
  }
  // ...
});

hypothalamus.on(allValidators, (result) => {
    if (Object.entries(allValidators).some(({isValid}) => !isValid)) {
        releaseHormone(validationFailed, true)
    } else {
        releaseHormone(submitDone, true)
    }
});

pureLit("my-form", (el) => {
    // those receptors will return their value only the render cycle
    //  after they were set, and in any other case return undefined
    const error = useReceptor(el, validationFailed) ?? false
    const success = useReceptor(el, submitDone) ?? false
    return html`
        <error-text show=${error}>Validation failed</error-text>
        <success-text show=${success}>Form submitted</success-text>
        <input-element name="firstName">FirstName<input-element>
        <input-element name="lastName">LastName<input-element>
        <button @click=${() => releaseHormone(doSubmit, true)}>submit</button>
    `
});