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

tiny-atom

v6.0.0

Published

Pragmatic and concise state management.

Downloads

228

Readme

🏄 global store for sharing application state easily ⚡️ highly optimised subscriptions and selectors ⚛️ supports React and Preact

History

Created in 2017, Tiny Atom aimed to offer:

  • A streamlined and user-friendly alternative to Redux
  • Compatibility with both React and Preact
  • Efficient re-rendering.

Tiny Atom achieved enhanced efficiency through:

  • Tracking and deduping nested subscription updates
  • Re-rendering only when there's a change in the computed state selection.

However, with the introduction of useSyncExternalStore, React now inherently encompasses the first two aforementioned optimizations. At the same time the global state approach has been slowly falling out of fashion in favor of using higher level state abstractions like Suspense, Relay, React Query and most recently React Server Components as well as more atomic and modular strategies such as Recoil.

Although tiny-atom remains a robust and battle tested solution, it's worth considering contemporary alternatives. For instance, Zustand embodies similar global store concepts in a more modern package. Meanwhile, Kinfolk is a spiritual successor to tiny-atom offering more streamlined and powerful alternative for managing shared application state.

Installation

npm install tiny-atom

Example

import React, { useState } from 'react'
import { createAtom, Provider, useSelector, useActions } from 'tiny-atom'

const atom = createAtom({
  state: {
    user: null,
    err: null,
  },

  actions: {
    // perform async work in the actions!
    async auth({ get, set, actions }) {
      // read and write state multiple times
      // within a single action invocation!
      if (get().user) return

      const token = localStorage.getItem('access-token')
      const { user } = await fetch('/authenticate', { token })
        .then((res) => res.json())
        .catch((err) => set({ err }))

      // update atom or dispatch other actions!
      set({ user })
      actions.log()
    },
    log({ get }) {
      console.log(get().user.name)
    },
  },
})

export default function App() {
  return (
    <Provider atom={atom}>
      <Dashboard />
    </Provider>
  )
}

function Dashboard() {
  // actions are functions, no string constants!
  const { auth } = useActions()

  // subscribe to slices of state, derive state
  // only re-renders if the keys of the selected
  // object differ!
  const { user, err } = useSelector((state) => {
    return { user: state.user, err: state.err }
  })

  useEffect(() => {
    auth()
  }, [])

  if (!user) return <div>Loading</div>
  if (err) return <div>Yikes</div>
  return <div>Hello, {user.name}</div>
}

API

createAtom(options)

Create an atom. Options:

{
  state?: any;
  actions?: { [actionName: string]: ActionFunction };
  evolve?: (atom: Atom, action: any, actions: { [actionName: string]: ActionFunction }) => void;
  bindActions?: (dispatch: DispatchFunction, actions: { [actionName: string]: ActionFunction }) => void;
  debug?: DebugFunction;
}

state

The initial state of the atom. Default: {}.

actions

An object with action functions. Where every action is (params, payload) => void with params:

  • get() - get the current state
  • set(patch) - updates the state with the patch object by merging the patch using Object.assign
  • swap(state) - replace the entire state with the provided one
  • dispatch(type, payload) - same as atom.dispatch, dispatches an action
  • actions - actions prebound to dispatch, i.e. actions.increment(1) is equivalent to dispatch('increment', 1)

evolve

(atom, action, actions) => void

A reducer function that receives every dispatched action payload and calls the appropriate action function. The default implementation uses action.type to find the matching function in the actions object. Think of it as a place of setting up middleware or customising how actions get dispatched in the specific tiny-atom instance.

bindActions

(dispatch, actions) => void;

A complementary function to evolve that binds the provided actions to atom.dispatch. Together with evolve it allows customising how actions get dispatched in the specific tiny-atom instance.

debug

A function that will be called on each action and state update. The function is passed an info object of shape { type, atom, action, sourceActions, prevState }. Tiny-atom comes with 2 built in debug functions tiny-atom/log and tiny-atom/devtools.

atom.get()

Get current state.

atom.get()
atom.get().feed.items

atom.set(patch)

Update state by shallowly merging an update.

atom.set({ user })
atom.set({ entities: { ...get().entities, posts } })

atom.swap(state)

Replace the entire state with the provided value.

atom.swap(nextState)

atom.dispatch(type, payload)

Send an action

atom.dispatch('fetchMovies')
atom.dispatch('increment', 5)

atom.actions

A map of actions that are bound to the dispatch. For example, if your actions passed to atom are

const actions = {
  increment({ get, set }) {
    const { count } = get()
    set({ count: count + 1 })
  },
}

They will be bound such that calling atom.actions.increment(1) dispatches action with dispatch('increment', 1).

atom.observe(cb)

Register a callback that triggers when the atom changes. The function returns an 'unobserve' function to unregister the callback

const unobserve = atom.observe(render)
atom.observe((atom) => render(atom.get(), atom.actions))

atom.fuse({ state, actions })

Extend atom's state and the action object. Used for creating a combined atom from multiple slices of state and actions from several modules.

const state = {
  project: { name: 'tiny-atom' },
}

const actions = {
  star: ({ get, set }) => {
    set({ project: { starred: true } })
  },
}

// add extra state and actions to an existing atom instance
atom.fuse(state, actions)

React API

<Provider atom={atom} />

Provide atom instance created using createAtom as context to the render tree.

useAtom()

Get atom instance provided by the Provider. Note: typically you should prefer using useSelector and useActions instead of using atom directly.

useSelector(selectorFn, options)

Select a slice of state and subscribed to state changes. Full state is passed to selectorFn where any computed value can be returned. The component will only re-render if the computed value differs by shallowly comparing every key of the previous and updated computed object.

Options:

observe

type: boolean default: true in the browser, false on the server

Use this to control if the hook should subscribe to the store and re-renders on every change or simply projects the state on parent re-renders, but does not re-render on state changes.

useActions()

Get the bound actions.

const { increment, decrement } = useActions()

useDispatch()

Get the dispatch function.

const dispatch = useDispatch()
dispatch('increment')
dispatch('increment', { by: 5 })

createContext()

By default, useSelector and useActions are bound to the default tiny-atom context that is shared across the app. Use this to create an isolated context. Note: you will need to create a dedicated set of hooks using createHooks(AtomContext).

const { AtomContext, Provider } = createContext()

createHooks(AtomContext)

Create custom set of hooks tailored for a specific context.

const { AtomContext, Provider } = createContext()
const { useSelector, useActions } = createHooks(AtomContext)

React HOC API

Before the widespread use of React hooks, Tiny Atom utilized the Higher Order Component (HOC) pattern. The following functions enable the use of tiny-atom with this pattern.

connect

const map = (state) => ({ count: state.count })
const ConnectedComponent = connect(map, options)(Component)

Connects a component to atom and re-renders it upon relevant changes. Connected component will be passed an action props with the atom's actions and the mapped props (the props returned in the map function).

map

type: function default: null

Map atom state to props for your component. Upon changes to atom, the mapped props are compared to the previously mapped props and the connected component is only re-rendered if they differ. A shallow object diff is used in the comparison.

options.observe

type: boolean default: true in the browser, false on the server

Use this to control if the connector subscribes to the store or simply projects the state on parent re-renders.

<Consumer map={map} />

A render props style component that can be used inline of your component's render function to map the state similarly to how connect works. It supports the following props.

map

type: function default: null

Map atom state to props for your component. Upon changes to atom, the mapped props are compared to the previously mapped props and the connected component is only re-rendered if they differ. A shallow object diff is used in the comparison.

observe

type: boolean default: true in the browser, false on the server

Use this to control if the connector subscribes to the store or simply projects the state on parent re-renders.

createConnect(AtomContext)

Create an isolated connect bound to a specific AtomContext.

createConsumer(AtomContext)

Create an isolated <Consumer /> bound to a specific AtomContext.

Preact API

For Preact, import createAtom, Provider, Consumer and connect from tiny-atom/preact. The hooks based API for Preact is not currently available.