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

react-svelte-stores

v0.2.0

Published

Truly Reactive Stores for React. Inspired by [Svelte](https://svelte.dev/tutorial/writable-stores)

Downloads

13

Readme

react-svelte-stores

Truly Reactive Stores for React. Inspired by Svelte

npm i react-svelte-stores

Why?

  • I wanted a good set of primitives with which I could build custom state management solutions.

  • I wanted a "cleaner" API than React's Context.

  • Gateway drug to Svelte, or a way for people who already love Svelte to write Svelte-like code in React.

Recipes

FSM Audio Player

You can use react-svelte-stores to create a finite state machine component that can receive messages from other components. Let's implement a minimal audio player to demonstrate this pattern.

  • The discriminated union of states represents the vertices of a state diagram.
  • The switch cases in the reducer function represent the edges of a state diagram; the transitions between states.
  • Side effects are handled in useEffect

Player.tsx

import React, { FC, useEffect, useRef } from "react";
import { writable, useStoreState } from "react-svelte-stores";

// discriminated union of possible states.
type State =
  | { status: "loading" }
  | { status: "playing"; time: number }
  | { status: "paused"; time: number };

// discriminated union of possible actions
type Action =
  | { type: "LOADED" }
  | { type: "PLAY" }
  | { type: "PAUSE" }
  | { type: "UPDATE_TIME"; time: number };

const reducer = (state: State, action: Action): State => {
  // state transitions based on state ("status") and event ("action")
  switch (state.status) {
    // when in the "loading" state, only react to "LOADED" action
    case "loading":
      switch (action.type) {
        case "LOADED":
          return {
            ...state,
            status: "playing",
            time: 0,
          };

        default:
          return state;
      }

    // when in the "playing" state, react to "PAUSE" and "UPDATE_TIME" actions
    case "playing":
      switch (action.type) {
        case "PAUSE":
          return {
            ...state,
            status: "paused",
          };

        case "UPDATE_TIME":
          return {
            ...state,
            time: action.time,
          };

        default:
          return state;
      }

    // when in the "paused" state, only react to "PLAY" action
    case "paused":
      switch (action.type) {
        case "PLAY":
          return {
            ...state,
            status: "playing",
          };

        default:
          return state;
      }
  }
};

const createReducibleStore = (
  initialState: State,
  reducer: (state: State, action: Action) => State
) => {
  const { subscribe, update } = writable(initialState);

  return {
    subscribe,
    // react-svelte-store's update method takes a callback that receives the current state,
    // and returns the next state.
    // we can create a dispatch method by taking an action,
    // then passing the current state and the action
    // into the reducer function within the update function.
    dispatch: (action: Action) => update((state) => reducer(state, action)),
  };
};

const initialState: State = {
  status: "loading",
};

const playerFSM = createReducibleStore(initialState, reducer);

const Player: FC = () => {
  const playerState = useStoreState(playerFSM);

  const audio = useRef<HTMLAudioElement>(null);

  useEffect(() => {
    // side effects on state transitions
    if (playerState.status === "playing") {
      audio.current?.play();
    }

    if (playerState.status === "paused") {
      audio.current?.pause();
    }
  }, [playerState.status]);

  return (
    <div>
      <audio
        ref={audio}
        src=""
        onTimeUpdate={(e) =>
          playerFSM.dispatch({
            type: "UPDATE_TIME",
            time: e.currentTarget.currentTime,
          })
        }
      />
      {playerState.status !== "loading" && (
        <p>Current Time: {playerState.time}</p>
      )}

      {(() => {
        switch (playerState.status) {
          case "loading":
            return <p>loading...</p>;

          case "playing":
            return (
              <button onClick={() => playerFSM.dispatch({ type: "PAUSE" })}>
                pause
              </button>
            );

          case "paused":
            return (
              <button onClick={() => playerFSM.dispatch({ type: "PLAY" })}>
                play
              </button>
            );
        }
      })()}
    </div>
  );
};
  • Music applications commonly allow you to pause or play tracks from components other than the track player. We can do this by importing playerFSM and calling playerFSM.dispatch!
  • Because we need to know whether the player is playing or paused, we subscribe to the store state. In order to prevent unnecessary rerenders when the time is updated (we only care about the player status, not the time), we use useSelectedStoreState, which takes a selector function as its second argument.

OtherComponent.tsx

const OtherComponent: FC = () => {
  const playerStatus = useSelectedStoreState(
    playerFSM,
    (state) => state.status
  );

  switch (playerStatus) {
    case "loading":
      return null;

    case "playing":
      return (
        <button onClick={() => playerFSM.dispatch({ type: "PAUSE" })}>
          pause
        </button>
      );

    case "paused":
      return (
        <button onClick={() => playerFSM.dispatch({ type: "PLAY" })}>
          play
        </button>
      );
  }
};

This approach makes it (nearly?) impossible to reach impossible states, while making cross-component communication clean and easy. You can even dispatch actions without subscribing to the FSM store. This style of reducer function, which considers the previous state as well as the action, was inspired by this David K. Piano tweet

Persisted Service

API Reference

Hooks

useStoreState(store: IStore<T>): T

useSelectedStoreState(store: IStore<T>, selector: <T, R>(state: T) => R): R

  • Compatible with reselect

Stores

writable(initialState: T): IWritableStore<T>

readable(initialState: T, setCallback?: ReadableSetCallback<T>): IReadableStore<T>

persisted(initialState: T, storeKey: string): IWritableStore<T>

persistedAsync(initialState: T, storeKey: string, AsyncStorage: AsyncStorageStatic): IWritableStore<T>

Custom stores must expose the subscribe function to be usable with hooks.