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

@dhmk/redux-model

v2.0.1

Published

Models for redux

Downloads

57

Readme

redux-model

Library that helps you to write concise and boilerplate-free models for redux store.

Inspired by easy-peasy and zustand.

Install

npm install @dhmk/redux-model

Examples

Complete example

Javascript

const modelA = createModel((self) => ({
  value: 0,
  increment: action(() => (s) => ({ value: s.value + 1 })),
  decrement: action(() =>
    produce((s) => {
      s.value--;
    })
  ),
  incrementAsync: () => {
    setTimeout(() => self().increment());
  },
}));

const modelB = createModel(...)

const root = createRoot({
  modelA,
  modelB,
})

const store = createStore(root.reducer, root.enhacer)

Typescript

interface Counter {
  value: number;
  increment: Action;
  decrement: Action;
  incrementAsync: () => void;
}

const model = createModel<Counter>((self) => ({
  value: 0,
  increment: action(() => (s) => ({ value: s.value + 1 })),
  decrement: action(() =>
    produce((s) => {
      s.value--;
    })
  ),
  incrementAsync: () => {
    setTimeout(() => self().increment());
  },
}));

If you're not a fan of writing types, you can use a builder helper.

const model = createModel(() => {
  const self = build(
    {
      value: 0,
    },
    (action) => ({
      increment: action(() => (s) => ({ value: s.value + 1 })),
      decrement: action(() =>
        produce((s) => {
          s.value--;
        })
      ),
      incrementAsync: () => {
        setTimeout(() => self().increment());
      },
    })
  );

  return self;
});

Usage with createStore function

const root = createRoot(...)

const store = createStore(root.reducer, root.enhacer)

Usage with configureStore function (from redux-toolkit)

const root = createRoot(...)

const store = configureStore({
  reducer: root.reducer,
  enhancers: (e) => [root.enhancer].concat(e), // root.enhancer must come first
  middleware: (m) => m({ serializableCheck: false }) // need to turn this off, because we have functions in state
})

API

createModel((self, context) => state)

Takes a function which should return model's initial state. It's called with two arguments: a getter function which returns current model state in store and a context object. The context object has the following properties:

context

  • id - unique model id

  • path - array of string keys that defines location of the model state within global store state

  • dispatch - store dispatch function

  • getState - store getState function

config((self, context) => config)

You can add extra configuration for a model by using its config method.

  • reactions(add => [])

Reactions are run synchronously after actions. Matcher can be a predicate function or a string type. If it returns true then reaction is invoked.

  • effects(add => [])

Effects are run synchronously after an action has been dispatched and after all middleware, before returning result of the dispatched action. Matcher can be a predicate function or a string type. It it returns true then effect is invoked.

Reactions and effects can utilize ActionMatcher type. For example:

declare const externalAction: ActionMatcher<[number]>
...
reactions: add => [
  add(externalAction, (action) => {
    // payload will be inferred as [number]
  })
]
...
  • hydration((mergedState, providedState) => nextState)

Each model state gets a unique id upon creation. Whenever model's reducer receives a state argument with a different id (or undefined state), this function is called. The first argument is a new deeply merged state (model's initial state and the provided state) and the second argument is the provided state. It should return a full state object, which will be used then.

  • middleware(({dispatch, getState}) => next => action)

Redux middleware localized to model. It means that getState returns model's state, not global.

Example for redux-saga:

middleware: (api) => (next) => {
  function* saga() {
    yield takeEvery(self().increment.type, (action) => {
      console.log("action", action);
    });
  }

  const mw = createSagaMiddleware();
  const handler = mw(api)(next);
  mw.run(saga);
  return handler;
};

action((...args) => state => partialState)

Declares an action and reducer pair. Must be pure.

build(stateA, (action, privateAction) => stateB)

Typescript helper. Must be called synchronously inside createModel function.

createRoot(models)

Takes an object with models and creates reducer and enhacer. It also returns getState function, so you can use it to create connections between models:

const { getState } = createRoot({
  modelA: createModelA(),
  modelB: createModelB(() => getState().modelA.property),
});

createStore(reducer, initialState?, enhancer?)

Minimal redux store. It has no checks and warnings which original redux store has. Also, it misses observable symbol.

attach(fn, obj)

Typescript helper. Alias for Object.assign. Use it together with Attach type. It allows for this handy pattern:

...
load: Attach<(id: number) => Promise<void>, {
  request: Action
  resolve: Action<[Data]>
  failure: Action<[Error]>
}>
...

...
load: attach((id) => {...}, {
  request: ...
  resolve: ...
  failure: ...
})
...

// then use it like this:
self().load(1)
self().load.request()
...

remountAction

This action dispatches after root models were changed (after binding to store or calling .update() method). It has a single argument - an object with two maps of mounted and unmounted ids of models:

ModelAction<
  [
    {
      mount: Record<string, true>;
      unmount: Record<string, true>;
    }
  ]
>;

onMount(ctx)

onUnmount(ctx)

Helper predicates for reactions/effects. Example:

createModel(...).config((self, ctx) => ({
  reactions: add => [
    add(onMount(ctx), a => {
      // a is remountAction
    })
  ],
  effects: add => [
    add(onUnmount(ctx), a => {
      // a is remountAction
    })
  ]
}))

createActions<S>()((action, privateAction) => actionsOnly)

Helper to define actions bound to state S.

Types

PrivateAction<Args, State?>

Core action type. It's uncallable and only has type property that is a string.

Action<Args, State?>

Extends PrivateAction and makes it callable.

StateOf<T>

Returns state of a model

ModelAction<Args>

Declares an action type which payload has Args type.

ActionMatcher<Args>

Declares a type which matches actions with compatible parameters. For example:

declare const a1: Action;
declare const a2: Action<[number]>;
declare const a3: Action<[number, string]>;
declare const a4: Action<[string]>;

// expects an action which has at least one parameter of type `number`.
declare function test(a: ActionMatcher<[number]>);

test(a1); // error
test(a2); // ok
test(a3); // ok
test(a4); // error