@solid-primitives/composite

Composable reactive helpers built around watching computation changes.

Usage no npm install needed!

<script type="module">
  import solidPrimitivesComposite from 'https://cdn.skypack.dev/@solid-primitives/composite';
</script>

README


Name: composites Stage: 2 Package: "@solid-primitives/composite" Primitives: createCompositeEffect, createCompositeComputed, createCompositeMemo, createCompositeRenderEffect, createModifier Category: Reactivity

@solid-primitives/composites

lerna size size

Composable reactive primitives, extending the createEffect, createComputed, createMemo behaviors using composable and reusable modifiers.

  • createCompositeEffect - When used alone, it works as createEffect(on()). But it can be combined with a set of Modifiers extending it's functionality.

  • createCompositeComputed - Similar to createCompositeEffect, but it uses createComputed instead.

  • createCompositeMemo - Apply the same modifiers to createMemo.

  • createCompositeRenderEffect - A createCompositeEffect that runs during the render phase as DOM elements are created and updated but not necessarily connected.

  • createModifier - A utility for creating your own custom modifiers. Each available modifier has been made using it.

List of officially available modifiers

How to use it

createCompositeEffect

const [counter, setCounter] = createSignal(0);

// alone:
createCompositeEffect(counter, n => console.log(n));

// accepts "defer" option, same as on()
createCompositeEffect(counter, n => console.log(n), { defer: true });

// with filter:
createCompositeEffect(debounced(counter, n => console.log(n), { wait: 300 }));

// with nested filters:
const { stop, pause } = createCompositeEffect(stoppable(pausable(counter, n => console.log(n))));

createCompositeComputed

The usage is the same as createCompositeEffect

const { ignoring } = createCompositeComputed(
  ignorable(counter, n => console.log(n)),
  { defer: true }
);

createCompositeMemo

Works simmilary to the ones above, but there are some differences:

  • The returned value will be: Accessor if you don't use modifiers, or [Accessor, ModifierReturns] if you do.
  • Accepts additional equals in options - a custom comparison function
const double = createCompositeMemo(counter, n => n * 2, { value: 0 });

// if you don't set the "value" option, the memo might return undefined
const [double, { pause, resume }] = createCompositeMemo(
  pausable(counter, n => n * 2),
  { value: 0 }
);

createCompositeRenderEffect

const { ignore } = createCompositeRenderEffect(stoppable(ignorable(counter, n => n * 2)));

Available Modifiers

import {
  stoppable,
  once,
  atMost,
  debounce,
  throttle,
  whenever,
  pausable,
  ignorable
} from "@solid-primitives/composite";

stoppable

returns { stop: StopEffect }, that can be used to manually dispose of the effects.

const { stop } = createCompositeEffect(stoppable(counter, n => console.log(n)));

once

disposes itself on the first captured change. Set the defer option to true, otherwise the callback will run and dispose itself on the initial setup.

createCompositeEffect(
  once(counter, n => console.log(n)),
  { defer: true }
);

atMost

you specify the number of times it can triggered, until disposes itself.

const { count } = createCompositeEffect(atMost(counter, n => console.log(n), { limit: 8 }));

debounce

debounces callback

const position = createScrollObserver();

createCompositeEffect(debounce(position, x => console.log(x), 300));

throttle

The callback is throttled

const position = createScrollObserver();

createCompositeEffect(throttle(position, x => console.log(x), 300));

whenever

Runs callback each time the source is truthy.

setInterval(() => setCount(p => p + 1), 1000);

createCompositeEffect(
  whenever(
    () => count() > 5,
    () => console.log(count())
  )
);
// will fire on each count change, if count is gt 5
// => 6, 7, 8, 9, 10, ...

createCompositeEffect(
  whenever(
    createMemo(() => count() > 5),
    () => console.log(count())
  )
);
// will fire only when the memo changes
// => 6

pausable

Manually controll if the callback gets to be executed

const { pause, resume, toggle } = createCompositeEffect(
  pausable(counter, x => console.log(x), { active: false })
);

ignorable

Somewhat similar to pausable, but ignore changes that would cause the next effect to run.

Preferably use this with createCompositComputed. As for createCompositEffect: since Solid batches together changes made in effects, the usage inside and outside effects will differ.

const { ignoreNext, ignore } = createCompositComputed(ignorable(
   counter,
   x => {
      // next effect will be ignored:
      ignoreNext()
      setCounter(p => p + 1)

      // is using createCompositEffect, this also will be ignored
      // but in createCompositComputed it won't be
      setCounter(5)
   }
));


const ignoreMe = () => {
   ignore(() => {
      // both changes will be ignored:
      setCounter(420)
      setCounter(69)
   })
   // but not this one:
   setCounter(p => p + 2)
}

// this watcher will work normally,
// ignoring only affects the one above
createCompositeEffect(counter, () => {...})

createModifier

A utility for creating your own modifiers, it accepts two arguments:

  1. A callback modifier — function that is executed when your modifier gets attached to the createComposite___. Here you get to modify the effect callback by attatching your logic to it.

  2. A boolean, that if true requires the usage of inner createRoot to provide a stop() function for disposing of the effect permanently.

// types:
function createModifier<Config, Returns, RequireStop>(
  cb_modifier: (
    source: Fn<any> | Fn<any>[], // like source of "on"
    callback: EffectCallback, // like callback of "on"
    config: Config, // config for your modifier
    stop: StopEffect | undefined // a StopEffect if RequireStop
  ) => [CustomCallback, Returns], // return your modified callback and custom return values
  requireStop?: RequireStop // true if you want to use StopEffect
): Modifier {}

// modifier that doesn't use "stop()"
const yourModifier = createModifier<{ myOption: boolean }, { value: string }>(
  (source, callback, config) => {
    const modifiedCallback = (...a) => {
      // is's important to run the previous callback here (modified callback of previous modifier)
      // also to be compatable with `createCompositeMemo` this should return a value
      return callback(...a);
    };
    return [modifiedCallback, { value: "this will get returned" }];
  }
);

// modifier that does require "stop()"
// notice the double "true" to use stop()
const yourModifier = createModifier<void, { value: string }, true>(
  (source, callback, config, stop) => {
    const modifiedCallback = (...a) => {
      /* here you can use stop() */
      return callback(...a);
    };

    return [modifiedCallback, { value: "this will get returned" }];
  },
  true
);

See the implementations of official modifiers for better reference.

Changelog

Expand Changelog

1.0.0

Initial realease

Acknowledgements

Designed and developed by thetarnav based on: