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

@b08/redux-generator

v2.0.3

Published

generator for redux members

Downloads

53

Readme

@b08/redux-generator, seeded from @b08/generator-seed, library type: generator

generator for redux members

Flaws of Redux

Redux offers a way to manage application state that has 2 key advantages: 1) your code is pure 2)no change propagation graphs, all code is linear and sequential. This makes your application way more stable and less resistant to modifaction, i.e. reduced risk of regression. However redux comes with a number of flaws, which sometimes shy developers away from using it. Same goes for Flux, and more.

Redux guideline bears antipattern

At first, I wanted "noizy syntax" to be on top, but then I decided that antipattern is worse of a flaw than some extra boilerplate redux makes you write. Because developers will eventually write switch-inside-switch everywhere, given it is allowed here. Worse, it is not even allowed, it is a guideline. Here is how typical reducer is suppesed to be written

function reducer(prev: YourState, action: IAction): YourState {
  switch (action.type) {
    case actions.type1:
       // pile of code
    case actions.type2:
       // another pile
    default: return prev;
  }
}

This code violates single responsibility principle. Two piles of code doing 2 different tasks reside inside one function. This becomes even worse when code requires a switch of its own. To avoid SRP violation, it is recommended to decompose contents from the switch. Like this:

function reducer(prev: YourState, action: IAction): YourState {
  switch (action.type) {
    case actions.type1:       
       const a1 = action as YourAction1;
       return pureFunction1(prev, a1.arg1, a1.arg2);
    case actions.type2:
       const a2 = action as YourAction2;
       return pureFunction2(prev, a2.arg3);
    default: return prev;
  }
}
// might even want to extract these functions to another file
function pureFunction1(prev: YourState, arg1: Type1, arg2: Type2): YourState {
  // pile of code
}

function pureFunction2(prev: YourState, arg3: Type3): YourState {
  // another pile of code
}

This way you are free to move the code around, and you are free to decompose it further until it is good enough for your taste.

Noizy syntax

But this decomposition from previous point makes syntax even noizier, there is one more file to touch. To make any, however small change in your application state, you need to write the following:

  1. pure function implementing your change
  2. new case for a reducer
  3. new action and action type
  4. new dispatcher function, to create action and dispatch it to the store That is a lot of of writing. It looks to be so much of an overhead compared to old good direct assignment - "state.field = newValue;". And it scares away developers from starting to use redux in their application.

Performance

While you state is small, there is no problem with performance, but when it grows, it can become a real issue. And here is why. To handle a list of child reducers a parent reducer is supposed to be created with combine reducers. So for each action, each child reducer will be called and new object is created for each parent reducer in the tree. So for application with 50 pages and average of 5 components on each page, 50 root reducers and 250 "leaf", all of them being called for each action. Sooner or later it will make you want to split the store into several stores. Or attempt some other optimization.

Selectors

Sometimes you would need a tree with depth more than 2, and it will make you write long selectors like this

const componentState = store.getState().pages.myPage.leftPanel.myComponent;

This makes the component to "know" where its state reside in the tree. As an alternative you might want to use libraries like "reselect" which might be useful, but always looked like workaround rather than proper solution.

Reducers are not reusable

If you want to use one component in two places, both states will be changing simultaneously. To work around that, you will need to introduce some sort of id to the state and action, and check that id in the reducer for every action. Also, when one component is in two different nodes of the tree, a component can't know where it resides in the tree, making use of selectors even worse.

Redux generator

The generator, as it is implied, was written to deal with those flaws. First flaw to go is "noizy syntax" since generator writes all the boilerplate code for you. Pure functions(which developer writes) and state description are used as origins for code generation. Reducer, actions, action types, dispatchers and selectors are a projections of those origins. In other words, developer flow looks like this: when you write new function modifying the state, generator will automatically render a new action for it, new action type, new case in the reducer, new method in the dispatcher class and new selector.

State definition

State should be defined before anything is generated. Here is an example

// file1
export interface MyComponentState {
  stateId: number;
  someFlagComponentNeeds: boolean;
  componentData: ComponentDataType[];
  textToDisplay: string;
}

// file2
export interface MyAppState {
  stateId: number;
  component1: MyComponentState;
  component2: MyComponentState;
  titleText: string;
}

These 2 interfaces define a tree consisting of 3 nodes, even though there are only 2 interfaces, one interface defines 2 nodes of the tree. And, you should've noticed, unlike usual redux guidelines, this guideline allows to define both data and children fields in any node of the tree.

Pure functions changing the state

To modify the state you need to define a function, first parameter of which is the state it changes, as well as return type.

export function setTitle(prev: MyAppState, titleText: string): MyAppState {
  return { ...prev, titleText };
}

Alternatively, if there are several functions for one state, you can enclose them into a class with state as a constructor parameter. Much like partial application. All return types should be explicitly defined, arrow function are not supported.

export class MyComponentReduction {
  constructor(private prev: MyComponentState) { }

  public setFlag(flag: boolean): MyComponentState {
    return { ...this.prev, someFlagComponentNeeds: flag };
  }

  public sortData(): MyComponentState {
    return { ...this.prev, componentData: this.prev.componentData.sort((d1, d2) => ... ) };
  }
}

That's it, this is all you need to write, regarding managing the state, the rest is generated. As you might have noticed, you do not need to write a switch, thus no more writing "antipatern" code.

Output of the generator

  1. Based on function name and name of the state it works with, action type is generated.
  2. Action is generated, having action type set permanently in it. Name of the action looks like A1, A2 and so on. One action per state-changing function. Each action also has stateId as a parameter, regardless of it being defined in corresponding state or not.
    All actions and action types are put into the single file called "actions.ts" which is dropped next to your root state.
  3. Only one reducer is generated for the whole tree and placed in "reducer.ts". There is no need for combine reducers anymore. It is known beforehand which action is processed at what node of the tree and by which function. So there is only one switch containing as much cases as there are actions associated to all states in the tree.
    And for each state there is a function that changes the state in the tree in most optimal way. No more "performance" flaw.
    Also, if state has an id and is present more than once in the tree, action having one id gets one tree path, action with another id gets another tree path.
  4. Selectors are generated for each state. I placed them in the same file as reducer, even though they could be put into their own file. It eliminates the need for the component to know where in the tree its state resides.
    When state has id and it is present in several nodes in the tree, selector will return state that corresponds to given id. And not only its own stateId can be passed as a parameter, but also id of its parent state or child state. When id of any other node in tree is passed to a selector, state of specified type, closest to specified node will be returned.

State id

Generated reducer and selectors are aware of the stateId. If you intend to use the same state in two branches of your state tree, add "stateId: number" field to your state. Dispatcher constructor will also be generated with second parameter. First parameter is the store, second parameter will be stateId. And you can provide not only the id of the state itself, but also id of the parent or child of that state and it will be automatically resolved to state's own id. If you pass 0 instead of id, then all the states of that type in the tree will be changed by dispatched action.

Limitations

  1. Each action can only be served by one function.
  2. "stateId" field in each state is reserved and can not be used for user data.
  3. State with id is not recommended to be a child of the state without id.

Generating redux members

import { generate } from "@b08/redux-generator"; import { transformRange, transform } from "@b08/gulp-transform"; import * as changed from "gulp-changed";

const options = { lineFeed: "\n", quotes: """ };

export function reduxMembers(): NodeJS.ReadWriteStream { // this is a gulp task
return gulp.src("./app/**/*.@(state|reduction).ts") .pipe(transformRange(files => generate(files, options))) .pipe(changed(dest, { hasChanged: changed.compareContents })) .pipe(logWrittenFilesToConsole) .pipe(gulp.dest("./app")); }

const logWrittenFilesToConsole = transform(file => { console.log(Writing ${file.folder}/${file.name}${file.extension}); return file; });