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

cross-state

v0.41.4

Published

(React) state library

Downloads

483

Readme

npm badge bundlejs badge

State library for frontend and backend. With React bindings.

Getting started

Install

npm install cross-state

The basics

cross-state provides a number of tools to manage state in your application. The most important building blocks are: stores (createStore) for global state and caches (createCache) for e.g. wrapping api calls. They can be used in any JavaScript environment and are not tied to any specific framework. React bindings are provided and can be used to easily integrate cross-state in your ui.

React bindings

You can use cross-state with React by importing the respective hooks - e.g. useStore.

import { useStore } from 'cross-state/react';

function Counter() {
  const counter = useStore(store, (state) => state.counter); // with or without selector
  return <div>{counter}</div>;
}

Or you can register the react bindings with the Store and Cache prototypes.

// Somewhere in your app setup
import 'cross-state/react/register';

function Counter() {
  const counter = store.useStore((state) => state.counter); // with or without selector
  return <div>{counter}</div>;
}

Stores

Create a store

The state can be any value, e.g. a number, an object or an array.

export const store = createStore({
  counter: 0,
});

Get the current state

const state = store.get();

Update the store

Pass in a new state or a function that updates the current state.

store.set((state) => ({
  counter: state.counter + 1,
}));

Subscribe to changes

const cancel = store.subscribe((state) => {
  console.log('New state:', state);
});

// Later, to unsubscribe
cancel();

Use the store in a React component

function Counter() {
  const state = store.useStore(); // without selector - be careful with this, as it will rerender on every state change
  const counter1 = store.useStore((state) => state.counter); // with selector - will only rerender when the selected value changes
  const counter2 = store.useStore('counter'); // with string selector

  return (
    <div>
      <div>{state.counter}</div>
      <div>{counter1}</div>
      <div>{counter2}</div>
    </div>
  );
}

Use the store in a React component with an update function

function Counter() {
  const [value, setValue] = store.useProp('counter');

  return (
    <div>
      <div>{value}</div>
      <button onClick={() => setValue((value) => value + 1)}>Increment</button>
    </div>
  );
}

Caches

Create a cache

export const user = createCache(
  async (org: string, id: string) => {
    const response = await fetch(`https://api.example.com/${org}/${id}`);
    const user: User = response.json();
    return user;
  },
  {
    invalidateAfter: { minutes: 10 }, // automatically invalidate the cache after 10 minutes
  },
);
  • invalidateAfter: Duration | ((state: ValueState<T> | ErrorState) => Duration | null) | null; - automatically invalidate the cache after a certain duration. You can also provide a function that returns a duration or null based on the current state of the cache:
export const cache = createCache([...],
  {
    invalidateAfter(({ status, value, error }) => {
      if (status === 'error') {
        return { minutes: 5 };
      }

      return value.expiresAt - Date.now();
    }),
    },
  },
);

Use the cache

const data = await cache('users', '123');

Use the cache in a React component

function User({ org, id }: { org: string; id: string }) {
  const [user, error, isLoading] = user(org, id).useCache();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return <div>{user.name}</div>;
}

Cache without parameters

When the cache does not have parameters or only optional parameters, you can use the cache without the parantheses.

const cache = createCache(async () => {
  return await fetch('https://api.example.com');
});

const data = await cache.useCache(); // equivalent to cache().useCache()

// or in a React component

const [data, error, isLoading] = cache.useCache();

Cache with connection

Experimental feature

A cache can be used not only for fetching data, but also for keeping it up to date with a WebSocket connection or similar.

// Explicit type annotations for content and cache keys are required here because TypeScript cannot infer them when using a connection.
export const cache = createCache<Service, [serviceId: string]>(
  (serviceId) =>
    async ({ connect }) => {
      // optionally wait until the connection is established, before fetching the initial data
      // that ensures that no updates are missed
      await connect(({ updateIsConnected, updateValue, updateError, close }) => {
        const ws = new WebSocket(`wss://api.example.com/service/${serviceId}`);

        ws.addEventListener('open', () => updateIsConnected(true));
        ws.addEventListener('close', close);
        ws.addEventListener('message', (event) => {
          try {
            const data = JSON.parse(event.data);
            updateValue(data);
          } catch (error) {
            updateError(error);
          }
        });

        return () => ws.close();
      });

      // fetch the initial data
      return await fetch(`https://api.example.com/service/${serviceId}`);
    },
  {
    // no cache invalidation here, because the cache is kept up to date by the connection
    invalidateOnWindowFocus: false,
  },
);