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-create-conveyor

v1.3.11

Published

an immutable state management solution for react based on immerjs, just pick the paths

Downloads

43

Readme

react-create-conveyor🛸

A state-management solution for react in Provider-less mode
Based on immerJS
Once pick the paths, do whatever you want ⚽
✔ concurrent mode

Quick Start

npm i react-create-conveyor

or Edit Conveyor

Table of Contents

Example

import { createConveyor } from 'react-create-conveyor';

export const useCounter = createConveyor(0);
function Example () {
    const [count, setCount] = useCounter();
    // see, we don't need <Context.Provider> ( ఠൠఠ )ノ
}

Advanced usage

export const [useMyData] = createConveyor({
  count: 0,
  dog: {
    name: 'xiao bai',
    breed: '🐶',
    age: 2,
  },
  today: {
    toDos: ['task1']
  }
})

// pass a selector -> useMyData(selector)
// use getIn to pick value
const A = () => {
  const [toDos, setToDos] = useMyData(({ getIn }) => getIn('today', 'toDos')); // typescript helps analyze the path
  return <button onClick={() =>
    setToDos(draft => { // pass a producer -> setToDos(producer)
      draft.push('task2') // just push it, it will be immutable
    })
  }>A{toDos}</button>
}

// pass a value instead of producer function to setCount
const B = () => {
  const [count, setCount] = useMyData(({ getIn }) => getIn('count'));
  return <button onClick={() => setCount(count + 1)}>B{count}</button>
}

select

// use select to collect mappings
// use state to get values from root state
// use memo to cache calculation
const C = () => {
  const [dog, drawDog] = useMyData(({ select, getIn, state, memo }) =>
    select({ // 👈 select is recommended
      cName: getIn('dog', 'name'),
      cBreed: getIn('dog', 'breed'),
      fullName: state.dog.name + state.dog.breed,
      // memoName calculation is now dependent on dog.age
      memoName: memo(() => state.dog.name + state.dog.breed, [state.dog.age]),
    }));
  return <div>
    <button onClick={() => drawDog(draft => {
      draft.cName = 'da huang'; // just draw it, able to modify cName directly! 💥
      draft.cBreed += '🐕'; // next immutable state created by powerful immerJS
    })}>C full:{dog.fullName}</button>
    memo - depend on age: {dog.memoName}
  </div>
}

Why to use select?

The select method is to mark the result as a recombination object and then it could be checked by shallow-equal. And with select, the object result reference is able to be cached.

Why to use getIn?

The getIn method makes things easier. Once pick the paths, do whatever you want. For further info, refer to ts part getIn and useConveyor.

// getIn is not necessary
const D = () => {
  const [dogAge1, update1] = useMyData(({ state }) => state.dog.age); // no getIn
  const [dogAge2, update2] = useMyData(({ getIn }) => getIn('dog', 'age')); // with getIn
  return <div>
    D {dogAge1}
    {/* for no getIn, draft will be proxy of root state! */}
    <button onClick={() => update1(draft => { draft.dog.age++ })}>nogetIn dogAge+</button>
    {/* with getIn, there is no need to specify the paths repeatedly, just set age! */}
    <button onClick={() => update2(age => age + 1)}>dogAge+</button>
  </div>
}

useTask

Define method in component to modify state. And it is allowed to pass dependencies, with useCallback built-in.

// useTask to define method
const R = () => {
  const [dog, , useTask] = useMyData(({ getIn }) => getIn('dog'));
  const reset = useTask((dog, payload) => {
    dog.age = payload;
    dog.name = 'xiao bai';
    dog.breed = '🐶';
  }, [])
  return <button onClick={() => reset(2)}>reset DogAge {dog.age}</button>
}

Register Async Method

Register methods outside component to modify state.

export const [useMyDog, { register: myRegister, dispatch: myDispatch }] = createConveyor({
  owner: 'master',
  dog: {
    name: '加布兽(Gabumon)',
    breed: '🐶',
  }
})

// select & put which returned from selectToPut are safe in asynchronization
// action: { type, payload }
myRegister('EVOLUTION', (action, { selectToPut, state, done }) => {
  const { select: getDog, put: evolve } = selectToPut(getIn => getIn('dog'));
  console.log(`${state().owner}: ${getDog().name} ultimate evolution.`);
  setTimeout(() => { // to modify state in async callback
    evolve(draft => {
      draft.name = '钢铁加鲁鲁(Metal Garurumon)';
      draft.breed = '🐺';
    });
    done('=> 绝对冷冻气(Cocytus Breath)'); // try to resolve the promise return from dispatch
  }, 2000);
})

const E = () => {
  const [dog] = useMyDog(({ state }) => state.dog);
  return <button onClick={() =>
    myDispatch({ type: 'EVOLUTION' }).then(ability => console.log(dog.name + ability))
    // ability will be printed after all impacted components finish rerender!
    // so the promise returned from dispatch is safe
  }>E async 2s {dog.name} {dog.breed}</button>
}

Cancellation

// use step to make every step abortable
// the usage of step(next, error) is compeletely the same as promise.then(next, error)
myRegister('UPDATE_CANCEL', (action, { step, onAbort, abortSignal }) => {
  const mockApi = new Promise(res => setTimeout(res, 1000));
  step(mockApi) // pass a promise at first
    .step(() => console.log('mock api done.')) // done
    .step(() => new Promise(res => setTimeout(res, 1000)))
    .step(() => console.log('next step done.')) // won't be done, for 1500ms < 2000ms👇

  onAbort(reason => console.log('onAbort: ', reason)); // listen cancellation and unsubscribe automatically
  setTimeout(() => console.log(abortSignal.aborted), 3000); // check whether aborted
});

const abortCtrl = new AbortController(); // create an abortController, and pass its signal to dispatch
myDispatch({ type: 'UPDATE_CANCEL' }, abortCtrl.signal).catch(err => {
  if (err?.name === 'AbortError') console.log('cancel reason: ', err.message);
});
setTimeout(() => abortCtrl.abort('cancel assignment'), 1500); // cancel after 1500ms🚫

Modules

const [useGlobal, { assemble: globalAssemble }] = createConveyor({}); // created a global conveyor
const [, subInstance] = createConveyor(666); // two weeks later created a sub conveyor

// three month later found the sub conveyor was accessed more frequently than you thought at the beginning
globalAssemble('assembledNumber', subInstance); // just assemble it, it's ok

const F = () => {
  const [fNum, setCount] = useGlobal(({ getIn }) => getIn('assembledNumber'));
  return <button onClick={() => setCount(fNum + 1)}>F{fNum}</button> // F666, get it in global conveyor
}

Debug Entry

Let's see something useful

const [, { autorun }] = createConveyor({ dog: { age: 2 } });
// provide the prop path (multiple props is allowed, as it is a 2D array)
// just debug happily
// and with breaking point, to find out where changes happened clearly in function call stack
autorun([['dog', 'age']], changed => {
  console.log(JSON.stringify(changed)); // consle log: [{"pre":2,"next":3}]
})

Typescript

It's recommended to use typescript to get exact type infer and error tips. But if you don't, you can also get type information from IDE such as vscode for its built-in type infer ability.
First to create a conveyor.

const [useMyData] = createConveyor({ count: 0, today: { toDos: ['task1'] }})

path infer for getIn

useMyData(({ getIn }) => getIn('today', 'toDos')) // ✔
useMyData(({ getIn }) => getIn('today', 'toDoss')) // ❌
// typescript error for missing 'toDoss' will be like this:
// Type '["today", "toDoss"]' is not assignable to type 'readonly ["today", "toDos"]'.
//     Type at position 1 in source is not compatible with type at position 1 in target.
//       Type '"toDoss"' is not assignable to type '"toDos"'.ts(2345)

useConveyor

useMyData is actually the hook useConveyor renamed by yourself Only editable props will exist on draft, owe to getIn

const [data, update] = useMyData(({ select, getIn, state }) => select({
  double: state.count * 2, // readonly
  thatIsCount: getIn('count'), // editable
  todayToDos: getIn('today', 'toDos') // editable
}))
// data type will be
// {
//   double: number;
//   thatIsCount: number;
//   todayToDos: string[];
// }

// update type will be
// ModifyFunction<{
//   thatIsCount: number;  -->  editable
//   todayToDos: string[];  -->  editable
// }>

todayToDos is only allowed to contain string element

update(draft => {
  draft.todayToDos.push(123) // ❌ Argument of type 'number' is not assignable to parameter of type 'string'.ts(2345)
})

image that if someone delete thatIsCount in selector while update function refers to it somewhere, you will get typescript error below

update(draft => {
  draft.thatIsCount++ // ❌ Property 'thatIsCount' does not exist on type '{ todayToDos: string[]; }'.ts(2339)
})

if you want to pass data and update to child component with type definition, the only way is to declare type for them as below

type UpdateFn = ModifyFunction<{ // import ModifyFunction type
  thatIsCount: number;
  todayToDos: string[];
}>

Api Reference

basic

|Method|Description| |-|-| |createConveyor|create a conveyor| |useConveyor|const [useConveyor] = createConveyor({}) actually it can be renamed like [useMyData] refer to useConveyor| |getIn|useConveyor(({ getIn }) => getIn('dog', 'age')) refer to getIn| |state|useConveyor(({ state }) => state.dog.age)| |select|useConveyor(({ select }) => select({ prop: value }) refer to select| |memo|useConveyor(({ select, memo }) => select({ memoValue: memo() })) refer to memo| |updateFn|const [, setToDos] = useConveyor() can be renamed refer to setToDos| |useTask|const [, , useTask] = useConveyor() can be renamed refer to useTask|

dispatch

|Method|Description| |-|-| |register|const [, { register }] = createConveyor({}) register method to modify state refer to register| |dispatch|const [, { dispatch }] = createConveyor({}) refer to dispatch when dispatch an action, the method registered will be called dispatch: (action: { type: string, payload?: any }, abortSignal?: AbortSignal) => Promise<unknown>| |selectToPut|register('ACTION_TYPE', (action, { selectToPut }) => {}) select the props which you want to modify refer to async| |select & put|const { select, put } = selectToPut() one for getValue, another for setValue. They are safe in async callback| |state: Function|register('ACTION_TYPE', (action, { state }) => {}) able to get latest version of state in async callback| |done|register('ACTION_TYPE', (action, { done }) => {}) resolve the promise returned from dispatch refer to async| |fail|register('ACTION_TYPE', (action, { fail }) => {}) reject the promise returned from dispatch| |step|register('ACTION_TYPE', (action, { step }) => {}) make promise chain abortable refer to cancel| |onAbort|register('ACTION_TYPE', (action, { onAbort }) => {}) refer to cancel| |abortSignal|register('ACTION_TYPE', (action, { abortSignal }) => {}) refer to cancel|

other

|Method|Description| |-|-| |assemble|const [, { assemble }] = createConveyor({}) refer to modules| |autorun|const [, { autorun }] = createConveyor({}) refer to debug|

Drawback

Similar to react-redux, any state change will trigger notification to all connected components for checking. So please don't put all data into a single global conveyor if you could. But the good news is that react-create-conveyor dosen't rely on Context/Provider, it is very easy to create and use conveyors whenever and wherever! It's decentralized.


other nice solutions: mobx, jotai, zustand, recoil
which are designed to be more comprehensive 🦸