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

@bearbytes/karma

v1.1.1

Published

State management for React, comparable but different to Redux

Downloads

28

Readme

@bearbytes/karma

This is a library in the Redux family of state management solutions, featuring a different set of tradeoffs. It is meant to be used with React (or React Native).

It has these design goals:

  • Support Typescript out of the box
  • Have React Hooks integrated
  • Require as little boilerplate code as possible
  • Allow for more flexibility in how to structure code

Differences to Redux:

  • No actions (or action creators, or action type constants)
  • No reducers
  • No immutable state updates, instead we can mutate state directly (thanks to immer)

Pros & Cons

  • Pro: It's dead simple. The API surface is minimal, it's just get, subscribe, update
  • Pro: No need to think about how to apply updates to deeply nested state in an immutable way
  • Pro: To implement a state update, you only need to write code in one place (with Redux, code for a new action is usually split up in 2 or 3 different places)
  • Con: Since the library does not enforce a specific structure, code can get messy in larger projects, unless you are disciplined enough to create your own structure
  • Con: No named actions make it hard to debug and monitor where state updates are coming from
  • Con: Incompatible with Redux middlewares (but compatible with Redux DevTools)

Should you use it?

Probably not. For anything serious, stick to one of the more established solutions.
I'm dogfooding it for my own projects, but there are probably still bugs to be fixed.

Getting started

Install the dependency:
npm i @bearbytes/karma

Create some type definitions for your state:

interface ITodo {
  id: string
  title: string
  isDone?: boolean
}

interface IAppState {
  todos: ITodo[]
}

Create an initial state object:

const initialAppState: IAppState = { todos: [] }

Call createStore, which also creates typesafe hooks and a Context container. You often don't need everything that function returns, just take out what you need.

import { createStore } from '@bearbytes/karma'

const {
  // Global store instance. Most apps only need this one.
  store,

  // If you want to have multiple store instances in the app,
  // this Container can be put into the React component hierarchy
  // to create a new store context below it.
  Container,

  // Get access to the store from the nearest Container,
  // or the global instance if no Container is used.
  useStore,

  // This is the most important hook, which extracts data from the
  // store and updates the component whenever that data changes.
  useStoreState,

  // This hook can be used in a React component to create a callback to
  // update state. It has some advantages to calling store.update() directly.
  useStoreUpdate,
} = createStore(initialState)

// In most cases, only a single store is needed:
export { store, useStoreState, useStoreUpdate }

Reading from Store

Get the current value saved in the store:

const appState = store.get()

Subscribe to the current and all future values, using rxjs:

store.subscribe((s) => {
  console.log('AppState is now:', s)
})

Register a listener to a specific piece of data in a React component:

function TodoListComponent() {
  // this component will be re-rendered when `todos` are updated,
  // but not when a different part of the store is updated
  const todos = useStoreState((s) => s.todos)

  return (
    <>
      {todos.map((todo) => (
        <TodoListItem key={todo.id} todo={todo} />
      ))}
    </>
  )
}

Writing to Store

Instead of dispatching an action at one place and having code to update the state in another place, we simply inline this code:

store.update((s) => {
  s.todos.push({ id: 123, title: 'Stop writing stupid Todo apps' })
})

You are only allowed to mutate state within the update method of the store. Subscribers to the store will only see the updated state after the function exits. Karma uses immer under the hood, so the same rules apply (state must consist of plain objects and arrays without circular references).

If you use multiple stores in your application, make sure to get the correct one from a React component:

function AddTodoButton(props: { todo: ITodo }) {
  const store = useStore()

  function onPress() {
    store.update((s) => {
      s.todos.push(props.todo)
    })
  }

  return <button onPress={onPress}>Add</button>
}

A better way might be to use the useStoreUpdate hook, which will automatically wrap the update function in useCallback, which can avoid re-renders when passed down to child components:

function AddTodoButton(props: { todo: ITodo }) {
  const onPress = useStoreUpdate(
    // Updates done to the store when onPress is called
    (s) => {
      s.todos.push(props.todo)
    },
    // DependencyList passed to useCallback
    //all variables used in the update function should be put here
    [props.todo]
  )

  return <button onPress={onPress}>Add</button>
}

Writing to Store asynchronously

Dealing with asynchronicity is often an issue with Redux-like solutions. Usually, you have to think about whether to use Thunks or Sagas or something completely different.

With Karma, there is just the update function, which may never be async. Don't overthink it, just update the state synchronously whenever something relevant happens:

async function downloadMultipleFiles(urls: string[]) {
  // Set the loading state
  store.update((s) => {
    s.downloadInProgress = true
  })

  // Download files in parallel
  await Promise.all(urls.map(downloadSingleFile))

  // All downloading done
  store.update((s) => {
    s.downloadInProgress = false
  })
}

async function downloadSingleFile(url: string) {
  const data = await fetch(url)
  await storeFileOnDisk(url, data)

  // Update the state after each file done
  store.update((s) => {
    s.filesDownloaded.push(url)
  })
}

Redux DevTools integration

Should work out of the box. Note that since our "actions" are just anonymous lambda functions, all of them will be called "Anonymous Action".