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

loflux

v0.0.8

Published

Local Flux tool

Downloads

18

Readme

Loflux

A sort-of-Flux-like state management library for React for creating stores that are:

  • ✅ Flux-like (unidirectional data flow).
  • ✅ Hook-based.
  • ✅ Immutable via Immer.
  • ✅ Fully type-safe.
  • ✅ Not wrapped around your whole app.
  • ✅ Render-efficient.

Check out a live demo here! Interested in the classic TODO example? Here you go!. For a more dynamic example, check out this live demo.

Basic Example

Let's get you up and running with a simple example that illustrates how to use loflux.

Install the library

Install from NPM:

# Using NPM
npm install loflux

# Or using Yarn
yarn add loflux

Create a Store

Start by creating a store. Your store will be type-safe based on the initial state and actions.

import { createStore } from "loflux";

const { useData, dispatch, useActionResponse } = createStore({
  initialState: { name: "Jane Doe", age: 32 },
  actions: {
    changeName: (draft, name: string) => {
      draft.name = name;
    },
    changeAge: (draft, age: number) => {
      draft.age = age;
    },
    requestDestroy: () => {
    }
  }
});

Use Data from Your Store

Now, let's subscribe to some data that's in the store!

const { useData } = createStore({ ... });

const NameDisplay = () => {
  const name = useData((s) => s.name);
  return <h1>Hello, {name}!</h1>;
}

The argument to the useData hook here is a selector function used to pull out the data that you want. This component only re-renders when the name value changes!

Change Data in Your Store

Chances are, if you're using some sort of store, your data needs to change over time. Let's check that out.

const { useData, dispatch, ... } = createStore({ ... });

const NameInput = () => {
  const name = useData((s) => s.name);

  return (
    <input
      value={name}
      onChange={(e) => dispatch('changeName', e.target.value)}
    />
  );
}

The 'changeName' argument might look like a magic-string, but it's actually type-safe and based on the actions that you provide. Changing that to 'foobar' is going to really upset TypeScript.

Furthermore, the payload of that dispatched action is type-safe, too! Don't try to pass a number as the second argument if the action you're trying to dispatch isn't expecting it! In general, the last parameters of your actions are the last arguments you should pass through to the action when it's dispatched!

Subscribe to Actions

Sometimes you want to just respond to actions that pass through your store (e.g., pressing a button in one component triggers some sort of action in some other component that's far away).

const { dispatch, useActionResponse } = createStore({ ... });


const DestructionButton = () => {
  return <button onClick={() => dispatch('requestDestroy')}>Try to destory</button>;
}

const OverlayModal = () => {
  const [showConf, setShowConf] = React.useState(false);

  useActionResponse('requestDestroy', () => {
    setShowConf(true);
  });

  return showConf ? <div>Confirmation modal...</div> : null;
}

API

The createStore function

The createStore function creates a store with a given state shape and set of actions that can be dispatched, and returns the store object (you likely don't need this) and some utility functions to access the store data and dispatch actions. The signature is:

createStore = (options: { initialState, actions }) => ({ store, useData, dispatch, useActionResponse });

with the following options:

| Options | Type | Required? | Description | | --- | --- | --- | --- | | initialState | S extends any | ✅ | Initial state for store. The type of this will type the rest of your store. | | actions | { [key: string]: (draft: Draft<S>, payload: any) => void; } | ✅ | Map of actions that can be dispatched, and how they affect the store's state. Uses Immer.js, so draft can be acted on as if it was mutable. |

Here's a quick example:

import { createStore } from "loflux";

const { store, useData, dispatch, useActionResponse } = createStore({
  initialState: { name: "Jane Doe", age: 32 },
  actions: {
    changeName: (draft, name: string) => {
      draft.name = name;
    },
    changeAge: (draft, age: number) => {
      draft.age = age;
    },
    requestDestroy: () => {}
  }
});

The createStore().useData hook

The createStore function will return a useData hook that you can use to access data. The signature for this hook is:

createStore().useData = (selector: (state: State) => any) => ReturnType<typeof selector>;

Use this selector to pluck out the data that you need, or do any massaging you need. For advanced computed/derived values, consider using this in conjunction with React.useMemo.

The createStore().dispatch method

The createStore function will return a dispatch method that you can use to dispatch actions to the store. The signature for this hook is:

createStore().dispatch = (actionName, ...payload) => void;

You pass in an action name as a string (corresponds to the actions you declared when creating the store), and payload value(s) that matches the type of the payload parameter(s) of the declared action.

Here's an example of using more than one payload argument:

const { dispatch } = createStore({
  initialState: { name: 'Grant', age: 28 },
  actions: {
  	changeNameAndAge: (draft, name: string, age: number) => {
  		draft.name = name;
  		draft.age = age;
    }
  }
});

// ... later on

dispatch('changeNameAndAge', 'John Doe', 37);

The createStore().useActionResponse hook

The createStore function returns a useActionResponse hook that responds to specified actions that pass through the store. You indicate which action type(s) you want to listen for, and a callback to run when those actions occur.

The useStoreData hook

You probably don't need this hook, createStore().useData is an easy-access pattern for this. The useStoreData hook is a way to extract/subscribe to store data from a particular store. You provide a store and a selector that selects that data in the store that you want to subscribe to, and the hook gives you back the selected data. Updates to that data trigger re-renders. The signature is:

useStoreData(store: Store, selector: (state: State) => any);

Here's a quick example:

const { store: profileStore } = createStore({
  initialState: { name: "Jane Doe", age: 32 },
  actions: { ... }
});

const MyComponent = () => {
  const name = useStoreDate(profileStore, s => s.name);
  
  return <div>Hello, {name}!</div>;
}

The useActionDispatcher hook

You probably don't need this hook, createStore().dispatch is an easy-access pattern for this. The useActionDispatcher hook is a way to dispatch actions to a specified store. You provide a store and the name of an action (from you action list provided in createStore), and the hook will return a function you can call to dispatch the specified action. The signature is:

useActionDispatcher(store: Store, actionName: string);

Here's a quick example:

const { store: profileStore } = createStore({
  initialState: {name: "Jane Doe", age: 32},
  actions: {
    updateName: (draft, newName: string) => {
      draft.name = newName;
    }
  }
});

const MyComponent = () => {
  const updateName = useActionDispatcher(profileStore, "updateName");
  return <button onClick={() => updateName("Susan!")}>Change to Susan!</button>;
}

The useActionEffect hook

You probably don't need this hook, createStore().useActionResponse is an easy-access pattern for this. The useActionEffect hook is a way to respond to actions that get dispatched. The signature is:

useActionEffect(store: Store, actionName: string | string[], effect: (newState: State) => void);

Here's a quick example:

const { store: profileStore } = createStore({
  initialState: {name: "Jane Doe", age: 32},
  actions: {
    updateName: (draft, newName: string) => {
      draft.name = newName;
    }
  }
});

const MyComponent = () => {
  useActionEffect(profileStore, "updateName", () => {
    console.log("updateName was fired! Do something cool.");
  });

  return <button onClick={() => updateName("Susan!")}>Change to Susan!</button>;
}

TODO:

  • [x] Actions can take more than one argument, spread thru args
  • [ ] Thunk support