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

@halka/state

v1.1.1

Published

A lightweight global state solution for react with zero external dependencies and clean hooks API

Downloads

193

Readme

@halka/state

Bundle Size npm version types license

@halka/state is a lightweight global state solution for react with zero external dependencies and clean hooks API.

Installation

npm install @halka/state

Examples

Creating a store

import { createStore } from '@halka/state';

const useCounter = createStore(0);

// OR

const useCounter = createStore(() => 0);

The library exports only one function i.e. createStore. You can use it to create a new store.

createStore accepts only one argument that is the initial state of the store. It can be anything a primitive value like number, string, boolean or an array or object. It returns a tuple. It can also accept a function that returns your initialState. This can be used to lazily initial your state.

The first element useCounter is a React hook that can be used inside any React functional component to select a slice of the current state from the store.

Using the state hook

function Counter() {
  const count = useCounter();

  return <span>{count}</span>;
}

The useCounter hook returns the selected slice of the current state.

Here, we do not pass any argument to the hook because we want the state as is from the store. But, the hook accepts two arguments -

  • selector - [optional] It accepts a callback function that gets passed the entire current state from the store and can return a partial slice of the state or derived state for the component to use. Use this to only subscribe to the state your component needs preventing unnecessary renders. By default, the entire state is returned.
  • equalityCheck - [optional] It accepts a callback function that gets passed the previous state slice (return value of the selector) as the first argument and the next state slice. It must return a boolean value (true if the state slice is to be updated, otherwise false). Use this to further fine tune your re-rendering logic. By default, a shallow comparison is done to check if there are any updates.

Using a state selector

const cubeSelector = (count) => count ** count;

function SquaredCounter() {
  const countCube = useCounter(cubeSelector);

  return <span>{countCube}</span>;
}

Here, we are passing a selector callback function that gives us the cube of the count state. This selector is used to get a derived state here.

Using a custom equality check

const absoluteValueCheck = (prev, next) => Math.abs(prev) === Math.abs(next);

function AbsoluteSquaredCounter() {
  const countCube = useCounter(cubeSelector, absoluteValueCheck);

  return <span>{countCube}</span>;
}

Here, we are passing a custom equality check function to check if the absolute value is the same as before. If yes, then we don't update it.

Updating state

import { createStore } from '@halka/state';

const initialCount = 0;
const useCounter = createStore(initialCount);

const reset = () => {
  useCounter.set(intialCount);
};

const increment = () => {
  useCounter.set((prevCount) => prevCount + 1);
};

function Counter() {
  const count = useCounter();

  const decrement = () => {
    useCounter.set((prevCount) => prevCount - 1);
  };

  return <span>{count}</span>;
}

State updater function set is accessible on the hook as a property itself.

You can pass the next state value directly to the function like we did in our reset handler.

But, if your next state depends on the previous state than you can also pass a callback function to the state updater which gets passed the previous state and must return the next state. Like we used in our increment and decrement handler.

This API is similar to state update function returned by useState.

Updating nested state with Immer

The re-render triggers are based on shallow referential equality checks. So, we shouldn't mutate the state directly updating nested properties in objects and arrays become a hassle. For example -

const markTodoComplete = (todoIndex) => {
  // we need to use map so that we create a new array
  // to trigger a state update (we shouldn't directly mutate it)
  // APIs like slice, map, filter, reduce return new arrays

  // iterate over all the todos, keep all the todos same except the one we are trying to mark as complete
  useTodos.set((prevTodos) =>
    prevTodos.map((todo, index) =>
      index === todoIndex ? { ...todo, completed: true } : todo
    )
  );
};

Not to worry, you can use the amazing Immer library to help with it. It lets you use APIs that mutate data while keeping the resultant data still immutable.

Let's look at how we can use immer to make the update above simpler.

import produce from 'immer';

const toggleTodo = (todoIndex) => {
  useTodos.set(
    produce((prevTodos) => {
      prevTodos[todoIndex].completed = true;
    })
  );
};

We are using the curried producer API from Immer.

To use immer in a more composable way, you can decorate the updateState itself in the following way -

import { createStore } from '@halka/state';
import produce from 'immer';

// some initial state value
import initialState from './initialState';

const useStore = createStore(initialState);

// compose the updater function with immer curried producer API
const updateStateWithImmer = (fn) => useStore.set(produce(fn));

// Then, the toggle todo example from above will look like this
const toggleTodo = (todoIndex) => {
  updateStateWithImmer((prevTodos) => {
    prevTodos[todoIndex].completed = true;
  });
};

Accessing state outside of React

You can access the state outside of react or without using the hook returned by createStore as well.

State getter function get is accessible on the hook as a property itself just like set.

const useStore = createStore(initialState);

const state = useStore.get();

You can use it even inside a useEffect to manually trigger updates and much more.

Inspirations and Prior work we referred

This library won't be possible without these and many more libraries existing in the open source communities. Big thanks to the whole community.