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

lacer

v0.0.1-rc4

Published

Simple and powerful state management for any application

Downloads

4

Readme

Lacer

Very simple and powerful state management solution for any application built on Laco and Immer.

Set up your stores and subscribe to them. Easy as that!

npm install lacer

Summary

  • :rocket: Simple to use
  • :tada: Lightweight (<2kbs gzipped)
  • :sparkles: Partial Redux DevTools Extension support (time travel thanks to Laco)

Example

import { Store } from 'lacer'

// Creating a new store with an initial state { count: 0 }
const CounterStore = new Store({ count: 0 })

// Implementing some actions to update the store
const increment = () => CounterStore.set((state) => state.count++)
const decrement = () => CounterStore.set((state) => state.count--)

increment()
expect(CounterStore.get().count).toBe(1) // Success

decrement()
expect(CounterStore.get().count).toBe(0) // Success

Redux DevTools Extension

Check out Redux DevTools Extension.

Time travel

Just click on the stopwatch icon and you will get a slider which you can play with. That's it! :)

React Native Debugger

Check out React Native Debugger.

Time travel

Works as you would expect :)!

API

Store<T>(initialState: T, name?: string)

// Initializing a new store with an initial state and a name:
interface INewStore = {
  count: number
}

const NewStore = Store<INewStore>({ count: 0 }, "Counter")

The name is optional and is used to get an overview of action and store relationship in Redux DevTools Extension. Action names for the Store will now show up as Counter - ${actionType} in DevTools Extension where as before only ${actionType} was shown.

Store.get(): T

// Getting the state of the store
Store.get()

Returns an object which could be something like { count: 0 } following the example.

Store.set(state: SetStateFunc, info?: string)

type SetStateFunc<T> = (state: Draft<T> => void) Technically, this function does allow any value to be returned to allow patterns similar to what you'll see in the example where the assignment is returned. However, the return value is ignored.

T inherits from the generic specified during the initalization of the Store. The SetStateFunc provides a Draft<T> from Immer. For more information on how to use Immer, check it out here.

// Setting a new state and passing an optional action name "increment"
Store.set((state) => (state.count++), "increment")

Store.replace(state: ReplaceStateFunc<, info?: String)

type ReplaceStateFunc<T> = (state: T) => T

T inherits from the generic specified during the initalization of the Store. This function allows you to completely replace the object used in the store and fires all subscription handlers afterwards regardless of the properties they listen to.

// Setting a new state and passing an optional action name "increment"
Store.replace((state) => { /* return modified state */}, "increment")

Store.addMiddleware(middleware: MiddlewareFunc)

type MiddlewareFunc<T> = (state: T, draft: Draft<T>, actionType?: string) => boolean | undefined

A middleware will intercept a state change (set or replace) before it is made. If the middleware returns false, the change is discarded. If it returns true or undefined, the next middleware will run. Setting values is identical to Store.set since it uses immer internally as well. For setting the state, use draft. For getting the state, use state.

Note: You can mutate this object freely since it is generated by Immer.

// Setting a condition to prevent count from going below 0
// and a special case for `SudoDecrement` action which CAN make count go below 0
CounterStore.addMiddleware((state, _, actiontype) => state.count >= 0 || actionType === 'SudoDecrement')

Store.removeMiddleware(middleware: MiddlewareFunc)

type MiddlewareFunc<T> = (state: T, draft: Draft<T>, actionType?: string) => boolean | undefined

The remove middleware function takes a middleware that matches a previously added middleware and removes it.

// Setting a condition to prevent count from going below 0
// and a special case for `SudoDecrement` action which CAN make count go below 0
const myMiddleware = (state) => (state.count++)
CounterStore.addMiddleware(myMiddleware)

CounterStore.set((state) => (state.count = 0))
expect(CounterStore.get().count).toBe(1) // Success

CounterStore.removeMiddleware(myMiddleware)
CounterStore.set((state) => (state.count = 0))
expect(CounterStore.get().count).toBe(0) // Success

Store.subscribe(listener: ListenerFunc, properties?: string[]): ListenerUnsubscribeFunc

type ListenerFunc<T> = (state: T, oldState: T, changes?: string[]) => void

A subscriber will fire whenever its properties match the properties changed during a set. It will always fire when a replace or reset is called. If properties is undefined, it will always fire on state change.

Note: DO NOT attempt to mutate the state object as it will throw an error.

// Setting a condition to prevent count from going below 0
// and a special case for `SudoDecrement` action which CAN make count go below 0
const unsubscribe = CounterStore.subscribe(
  (state, oldState) => console.log(state.count, state.oldCount),
  ['count']
)
CounterStore.set((state) => (state.count++)) // "1 0" printed to console

CounterStore.set((state) => (state.someOtherProperty++)) // Nothing printed to console

unsubscribe()
CounterStore.set((state) => (state.count++)) // Nothing printed to console

Store.unsubscribe(listener: ListenerFunc)

type ListenerFunc<T> = (state: T, oldState: T, changes?: string[]) => void

The unsubscribe function takes a listener function that matches a previously added subscription and removes it.

// Setting a condition to prevent count from going below 0
// and a special case for `SudoDecrement` action which CAN make count go below 0
const myListener = (state) => (console.log(state.count))

CounterStore.subscribe(myListener)
CounterStore.set((state) => (state.count++)) // "1" printed to console

CounterStore.unsubscribe(myListener)
CounterStore.set((state) => (state.count++)) // Nothing printed to console

Store.reset(force: boolean): boolean

// Resets the store to initial state
Store.reset()

Reset will bring back the original state of the Store when it was initialized. This is stored in Store.initialState. It will return true on success and false on failure. If force is true, it will run all middleware regardless of their success and will always reset the store.

Store.dispatch(value: any, info: String)

// Dispatching an action that does not change the state of the store
Store.dispatch(changeLocation(), "Location change")

You might want to dispatch an action that is associated with a certain store but don't want to change the state. The action will in this case be shown as StoreName - Location change.

dispatch(value: any, info: String)

import { dispatch } from 'lacer'

// Dispatching a global action that does not change any state
dispatch(changeLocation(), "Location change")

You might want to dispatch a global action that is NOT associated with any store. The action will in this case just be shown as Location change.

Testing

Testing using jest:

import { ICounterState } from '/types'
import { Store } from 'lacer'

test('CounterStore simple actions', () => {
  // Creating a new store with an initial state { count: 0 }
  const CounterStore = new Store<ICounterState>({ count: 0 }, 'Counter')

  // Implementing an action to update the store
  const increment = () => CounterStore.set((prev) => prev.count++, 'Increment')

  expect(CounterStore.get().count).toBe(0)

  increment()
  expect(CounterStore.get().count).toBe(1)
})

Credits

Based on:

Depends on:

Special Thanks