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

react-hook-immutable

v1.2.2

Published

react hooks for immutable states

Downloads

13

Readme

React Immutable

Installation

npm install react-hook-immutable
yarn add react-hook-immutable

Usage

import { useCallbackImmutable } from "react-hook-immutable";

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const handleClick = useCallbackImmutable((ev: MouseEvent) =>
    onClick(id, count)
  );
  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

The handleClick is immutable, but its body is refreshed at each rendering of React life cycle, and useCallbackImmutable is really more efficient than useCallback.

Context

In React life cycle, there is many rendering because of the state changes, that cause performance issues. The goal of this lib is to reduce the changes of useCallback, which updates each time one its dependency changes.

Everytime, the callback of useCallback only needs to read values and change states. Why should we have to change the function? Why can not we just read these states when called?

Analyse

☠ The worst solution

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const handleClick = () => onClick(id, count);
  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

Each time this component or its parents are rendering, the onClick event unassign previous callback and assign the new handleClick. It can happens many time and make performance issues.

⚠ The second worst solution

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const handleClick = useCallback(
    () => onClick(id, count),
    [id, count, onClick]
  );
  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

Now its better, we do not listen each component or parents rendering. But we still listen id, count, onClick and we now checks if these dependencies changes every time. Please note a useCallback is composed of a useState and a useEffect (the useEffect checks each rendering and can be heavy).

😓 The React solution (when dependencies are changing frequently)

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const idRef = useRef(id);
  useEffect(() => {
    idRef.current = id;
  }, [id]);

  const countRef = useRef(count);
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  const onClickRef = useRef(onClick);
  useEffect(() => {
    onClickRef.current = onClick;
  }, [onClick]);

  const handleClick = useCallback(
    () => onClickRef.current(idRef.current, countRef.current),
    []
  );

  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

A useCallback with no dependencies returns a callback considered immutable, it never changes. It's better but there is still several performance issues. We can merge useRef, but its value will be changed every time one of these values are changed.

The Solution Managing Props 👏

After many optimizations, the hook useCallbackBase has been created to get an immutable function & to manage props in a base object:

import { useCallbackBase } from "react-hook-immutable";

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const handleClick = useCallbackBase(
    { id, count, onClick },
    (base) => (ev: MouseEvent) => base.onClick(base.id, base.count)
  );

  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

The handleClick returned is immutable, and the useCallbackBase is only composed of 2 useState.

The Smart Solution 🤟

After many optimizations, the hook useCallbackImmutable has been created to match this purpose. It looks like useCallback but does not need any dependencies. The function read parent scope (and component changes in React life cycle) & the callback handleClick is immutable :

import { useCallbackImmutable } from "react-hook-immutable";

const Comp: React.FC<{
  id: string;
  count: number;
  onClick(id: string, count: number): void;
}> = ({ id, count, onClick }) => {
  const handleClick = useCallbackImmutable((ev: MouseEvent) =>
    onClick(id, count)
  );
  return <button onClick={handleClick}>Clicked {count} times!</button>;
};

The handleClick returned is immutable, and the  useCallbackImmutable is only composed of 1 useState.

Definitions

React life cycle

The React life cycle is defined by the React component function, executed at each rendering (because of some state change). A React life cycle starts by execution of component function, then it checks every useEffect which dependencies have changed (run the useEffect previous destructor and then the main callback). It does the same thing with useLayoutEffect, then useInsertionEffect. When the React component is no more used, the component triggers the last destructors left then disappear.

As the hooks are registered by index (not by name), the hooks should be the same count and at the same place in React component function.

Rendering

A rendering happens when a React component state changes. At first renderer, it registers the hooks used by the React component and execute for the last time the useState when their parameter is a function. Each time this React component state changes or one of its parents, it triggers a new rendering of the component.

Immutable

An immutable is a variable that never changes, even at a new rendering. Basic React immutable are the callback setState returned as second item of useState and the object ref returned by useRef. The object ref returned by useRef is immutable, but its current property can change. Also please note that the state as the first item returned by useState is immutable if the setState is never used.

Documentation

useImmutable

This hook instanciates a variable which is immutable in React life cycle. State definitiveState is the definitive state of your hook. The returned value immutable is the same value as definitiveState in first rendering (if it is a function, returns the returned value).

// declaration
declare const useImmutable: <T>(definitiveState: T | (() => T)) => T;
// run
const immutable = useImmutable(state);

useBase

This hook writes properties of entry in an immutable (in React life cycle) object base, which is usefull for referencing items that should be read only. Parameter entry is any object (the prototype and not enumerable properties of entry are ignored). The value returned is a new object immutable in React life cycle, with properties copied from entry, but without not enumerable properties.

// declaration
declare const useBase: <
  T extends Record<string, any>,
  R extends Record<string, any> = { [k in keyof T]: T[k] }
>(
  entry: T
) => R;
// run
const base = useBase({ state });
base.state;

useMemoBase

This hook let you trigger fn callback when dependencies list changes like a useMemo, but with a state entry registered in base as parameter of your fn callback. Parameter entry contains every values that should be read only, used in parameter base of fn callback. Parameter fn is a callback executed each changing of dependencies list, with base as first parameter. Parameter dependencies lists items listened on change for updating returned value by fn. If empty, fn will be executed at first rendering only, same as [] dependencies. The hook returns the returned value by fn.

// declaration
declare const useMemoBase: <
  Entry extends Record<string, any>,
  Fn extends (base: Entry) => any
>(
  entry: Entry,
  fn: Fn,
  dependencies?: any[]
) => ReturnType<Fn>;
// run
const computed = useMemoBase(
  { read },
  (base) => ({ result: base.read && dep }),
  [dep]
);
computed.result;

useCallbackBase

This hook creates an immutable callback, with component states entry registered in base as parameter of your fn callback.

// declaration
declare const useCallbackBase: <
  Entry extends Record<string, any>,
  Fn extends (base: Entry) => (...args: any[]) => any
>(
  entry: Entry,
  fn: Fn
) => ReturnType<Fn>;
// run
const callback = useCallbackBase(
  { read, onClick },
  (base) => () => base.onClick(base.read)
);
callback();

useCallbackImmutable

This hook creates an immutable callback. The function read parent scope & component changes in React life cycle.

// declaration
declare const useCallbackImmutable: <Fn extends (...args: any[]) => any>(
  fn: Fn
) => Fn;
// run
const callback = useCallbackImmutable(() => base.onClick(base.read));
callback();

useEntries

This hook registers a state entry each React life cycle rendering. Its parameter entry is the state registered. It returns an immutable object with methods to get entries.

// declaration
type EntriesMethods<Entry> = {
  getFirst: () => Entry;
  getLast: () => Entry;
  getEntries: () => Entry[];
  getUnique: () => Entry[];
  getChangingList: () => Entry[];
  countEntries: () => number;
};
declare const useEntries: <Entry>(entry: Entry) => EntriesMethods<Entry>;
// run
const entries = useEntries(state);
entries.getFirst();
entries.countEntries();

writable

A writable triggers listeners callback when its own data changes by a set or an update. The data should not be read outside of a subscribe. Parameter init is the initial value of this writable instance. Parameter reducer is an optional callback which parses input data (by set or update) before setting the writable.

// declaration
type WritableListener<State> = (value: State) => Void;
type WritableUpdater<State, Input> = (
  value: State,
  set: (value: Input) => void
) => Void;
type WritableReducer<State, Input> = (
  value: State,
  action: Input,
  set: (value: State) => void
) => Void;
type Writable<State, Input = State> = {
  subscribe(listener: WritableListener<State>): () => boolean;
  update(updater: WritableUpdater<State, Input>): void;
  set(value: Input): void;
  valueOf(): State;
  toString(): string;
};
declare const writable: <State, Input = State>(
  init: State,
  reducer?: WritableReducer<State, Input> | undefined
) => Writable<State, Input>;
// run
const storage = writable(
  Object.assign({ theme: "light" }, localStorage),
  (current, value: Partial<Storage>, set) => set({ ...current, ...value })
);

storage.subscribe((newValue) =>
  Object.entries(newValue).forEach(([field, value]) => {
    if (value === null || value === undefined)
      window.localStorage.removeItem(field);
    else window.localStorage.setItem(field, value);
  })
);

storage.set({ theme: "dark" });

useWritable

This hook returns a React state management of a writable. Parameter init is the writable, or initial value for writable construction. Parameter reducer is an optional function for reducing new value at update, used only if init is not a writable. The hook returns an immutable (do not change in React life cycle) list of arguments, where first item is the current value of writable, second item is an immutable updater of writable, third item is an immutable writable instance.

// declaration
type StateUpdate<State, Input> = (
  updater: State | ((current: State) => Input)
) => void;
type UseWritableOuput<State, Input = State> = [
  State,
  StateUpdate<State, Input>,
  Writable<State, Input>
];
type UseWritable = {
  <State>(init: Writable<State>): UseWritableOuput<State>;
  <State, Input = State>(
    init: State,
    reducer?: WritableReducer<State, Input>
  ): UseWritableOuput<State, Input>;
};
declare const useWritable: UseWritable;
// run
const [values, updateStorage, storage] = useWritable(storage);
updateStorage({ theme: "light" });
updateStorage(({ theme }) => ({ theme: theme === "light" ? "dark" : "light" }));

readable

A readable listen an event or a subscription and give its value for scripts subscribing it. Parameter reader is a callback with set as parameter, which should be called each time the readable value changes. The callback can return a callback for unsubscribing the set callback.

// declaration
type ReadableUpdater<State> = (newValue: State) => void;
type ReadableCallback<State> = (
  updater: ReadableUpdater<State>
) => undefined | (() => void);
type Readable<State> = {
  subscribe(listener: ReadableListener<State>): () => boolean;
  unsubscribe(): void;
  valueOf(): State;
  toString(): string;
};
declare const readable: <State>(
  reader: ReadableCallback<State>
) => Readable<State>;
// run
const storage = writable({ user: "me" });
const readStorage = readable((set) => storage.subscribe(set));

const geo = readable<GeolocationPosition>((set) => {
  const id = navigator.geolocation.watchPosition(set);
  return () => navigator.geolocation.clearWatch(id); // optional unsubscribe callback
});

useReadable

This hook returns a React state management of a readable. Parameter reader is a callback with set as parameter, which should be called each time the readable value changes. The hook returns an immutable (do not change in React life cycle) list of arguments, where first item is the current value of readable, second item is an immutable readable instance.

// declaration
type UseReadableOuput<State> = [State, Readable<State>];
type UseReadable = {
  <State>(reader: Readable<State>): UseReadableOuput<State>;
  <State>(reader: ReadableCallback<State>): UseReadableOuput<State>;
};
declare const useReadable: UseReadable;
// run
const [value, geo] = useReadable(geo);