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

unreduxed

v1.0.1

Published

a lightweight and simplest state management library for React.

Downloads

127

Readme

unreduxed

a library to never think about re-rendering of React components ever again

unreduxed is a state management library for React. This is inspired by

  • react-redux https://react-redux.js.org/
  • unstated-next https://github.com/jamiebuilds/unstated-next

features

  • simpler API

  • suppress extra re-rendering

  • split responsibilities by multiple containers

unreduxed is not opinionated library, so You just need to know usage of hooks and context.

a problem of unstated-next

unstated-next is very simple library. It just uses context to deliver state to other components. But when context changes, all components that subscribe to it will be re-rendered. This causes perfomance issues as the number of components subscribing to context increases.

unreduxed solves this problem. If state provided by context changes, but the value obtained by useContainer does not change, the component will not be re-rendered. This behavior is inspired by the useSelector of react-redux.

Getting Started

install

npm install unreduxed

create a container

We create a container by defining a common custom hook (let's call it a container hook) and passing it to unreduxed. The value that this container hook returns will be shared as a container with child components. The value returned by unreduxed is a fixed-length tuple containing ContainerProvider and useContainer (unstated-next returns a { Provider, useContainer } object, but this difference is just a preference).

import React from "react";
import unreduxed from "unreduxed";

const useCounter = () => {
  const [count, setCount] = React.useState(0);

  const increment = React.useCallback(() => setCount(prev => prev + 1), []);
  const decrement = React.useCallback(() => setCount(prev => prev - 1), []);

  return { count, increment, decrement };
};

export const [ContainerProvider, useContainer] = unreduxed(useCounter);

The container hook is just a custom hook, so we can do anything as long as the hook rules are followed (use useEffect, use a third-party hook, use another container by unreduxed, etc).

Place ContainerProvider

ContainerProvider is a component that internally executes useCounter to hold its state. Place it at the top of the component tree where you want to share the state. We can't use states outside of this Provider (you should know how to use context).

const Counter: React.FC = () => {
  return (
    <ContainerProvider>
      <Count />
      <CountButtons />
    </ContainerProvider>
  );
};

Retrieve a value with useContainer

We can use useContainer hook to retrieve the value from the container. useContainer takes the selector function as an argument. The selector function is defined so that the argument is a container and the return value is what you want to use there (same usage as useSelector in react-redux).

const Count: React.FC = () => {
  const count = useContainer(container => container.count);

  return <p>count: {count}</p>;
};

const CountButtons: React.FC = () => {
  const increment = useContainer(container => container.increment);
  const decrement = useContainer(container => container.decrement);

  return (
    <div>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
    </div>
  );
};

Here the CountButtons component is retrieving increment and decrement from the container, but not count. This will prevent CountButtons from re-rendering when the uninsteresting count changes. This is not possible with unstated-next, which uses the normal functionality of context as is.

API Reference

default exported function

type definition

function unreduxed<Container, Init = undefined>(useHook: (initialState?: Init) => Container): readonly [ContainerProvider, useContainer];

usage

function useAwesomeHook(initialValue?: number) {
  const [value, setValue] = React.useState(initialValue ?? 0);
  return { value, setValue };
}

const [ContainerProvider, useContainer] = unreduxed(useAwesomeHook);

description

You create a container by defining a custom hook (container hook) that returns a value and passing it as an argument. You can pass a initial value to the container hook via ContainerProvider described later. However, since the type definition makes it optional to pass the initial value to ContainerProvider, the argument of the container hook must take into account the possibility of undefined. If you are using TypeScript, you will get a compile error if you do not accept undefined.

ContainerProvider

type definition

type ContainerProviderProps<I, C> = ({ mock: C } | { initialState?: I }) & {
  children: React.ReactNode;
};

const ContaierProvider: React.FC<ContainerProviderProps<Init, Container>>;

usage

const App: React.FC = () => {
  return (
    <ContainerProvider initialState={2}>
      <ChildComponent />
    </ContainerProvider>
  );
};

description

If you pass a value to initialState, which is one of props, it will be passed as an initial value to the argument of the container hook. It follows the unstated-next API.

Also, if you pass a value to mock, which is one of props, the container hook will not be executed and instead mock will be provided by ContainerProvider.

const MockProvider: React.FC = () => {
  const mock = {
    value: 10,
    setValue: () => {
      console.log("setValue() called.");
    },
  };

  return (
    <ContainerProvider mock={mock}>
      <ChildComponent />
    </ContainerProvider>
  );
};

This means that you can inject any container when looking at it with a tool a tool like Storybook. However, never pass mock or initialState depending on the conditions in your production application. React raises an error because the hooks are executed in a different order. If you are using TypeScript, passing them at the same time will result in a compilation error.

useContainer

type definition

function useContainer(): Container;
function useContainer<T>(selector: (container: Container) => T, comparer?: (prev: T, next: T) => boolean): T;

usage

const ChildComponent: React.FC = () => {
  const value = useContainer(container => container.value);

  return <span>{value} is awesome !</span>;
};

description

This interface is inspired by the useSelector of react-redux.

You can get the entire container by using the useContainer hook without any arguments. However, you should use it for as many container values as you want to use, except when the container is returning only a single value. Because in most cases the return value of a container hook should return a different object each time (as in the Getting Started example). In that case, you can't take advantage of unreduxed, which avoids re-rendering, because you end up getting another object with useContainer each time.

const ChildComponent: React.FC = () => {
  const count = useContainer(container => container.count);
  const name = useContainer(container => container.name);

  return (
    <div>
      <p>Hello {name} !</p>
      <p>Your count is {count} !</p>
    </div>
  );
};

You can pass a comparer function as the second argument to the useContainer hook. This allows you to customize the determination of equivalence between the previous and next values (same API as useSelector in react-redux). If not specified, a comparison is made by ===.

const ChildComponent: React.FC = () => {
  const user = useContainer(
    container => container.user,
    (prev, next) => prev.userId === next.userId,
  );

  return (
    <p>
      {user.userId}: {user.userName}
    </p>
  );
};