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

redux-controllers

v0.0.40

Published

## Motivation Usually, working with Redux architecture feels very verbose and time-consuming. If you want to patch something very fast, you would feel that your hands are tightened. There should be a simple way to code using Redux patterns. It should be s

Downloads

13

Readme

Redux Controllers

Motivation

Usually, working with Redux architecture feels very verbose and time-consuming. If you want to patch something very fast, you would feel that your hands are tightened. There should be a simple way to code using Redux patterns. It should be simple as writing a class method. Hence Redux Controllers

Why Redux Controllers

You will find an easy way to create Redux stores and mutation methods that can be easily integrated with React/ React Native/ Angular codebase. You will also be able to write separate tests for Redux Controllers on NodeJs using Mocha.

Features

  • "Complexity" of Reducers/ Actions/ Action Creators/ Dispatchers taken of
  • Out of the box immutability management (inbuilt Immer)
  • Support for Asynchronous Commits
  • Helper functions to efficiently bind store to component
  • Out of the box persistent
  • Ability to use existing reducers and middleware along with Redux Controllers
  • Typescript Support
  • Application testing Framework
  • Watchers -> Actions and State
  • Inbuilt Caching

Get Started

  1. Install Redux Controllers using npm
npm i redux-controllers -s
npm i rxjs -s

or using yarn

yarn add redux-controllers
yarn add rxjs
  1. Create your first Redux controllers

Redux Controllers mainly consists of two parts

  1. Redux Store initialization function
  2. Controllers

controllers/counter/counter.controller.ts

import { RootState } from "../store";
import { ReduxController, ReduxControllerBase, ReduxAsyncAction, CommitFunction, ReduxAction, AutoUnsubscribe, ReduxEffect, ReduxWatch } from "redux-controllers";


export interface CounterState {
    counter: number,
}

@ReduxController((rootState: RootState) => rootState.counterState)
export class CounterController extends ReduxControllerBase<CounterState, RootState> {

    defaultState = {
        counter: 0
    }

}

controllers/store.ts

import { CounterState, CounterController } from "./counter/counter.controller";
import { Reducer, combineReducers } from "redux";
import { ReduxControllerRegistry } from "redux-controllers";
import { AsyncStorage } from "react-native"


export interface RootState {
    counterState: CounterState,
}

export function initStore() {
    ReduxControllerRegistry.init([
        CounterController,
    ], {
            environment: 'REACT_NATIVE',
            persistance: {
                active: true,
                throttle: 200,
                asyncStorageRef: AsyncStorage
            }
        });
        ReduxControllerRegistry.load();

}
  1. Start Redux Controller in the beginning of the app
import * as React from 'react';
import { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TextInput } from 'react-native';
import { initStore, RootState } from './controllers/store';


// Initiate Redux Stores
initStore();


export default class App extends Component<any, any> {

  render() {
    return (
      <View style={{}}>
          <TouchableOpacity onPress={this.login} style={styles.button}>
              <Text style={styles.buttonText}>Login</Text>
            </TouchableOpacity>
      </View>
    );
  }
}

Things to notice

  • Controllers have a reference to rootstate. This is for typings to work properly
  • Controllers themselves state which part of the Redux State they are controlling(mutating).
  • Default state is provided as a property
  • ReduxControllerRegistry accepts an array of ReduxControllers as the first parameter and accepts configuration option as the second parameter. The options should follow one of the interfaces based on the environment ReduxControllerOptions_web | ReduxControllerOptions_reactNative | ReduxControllerOptions_node
  • The interface definitions can be found here

Hola! You have created your first Redux controller 😃

Learn to use Redux Controller

Create your first ReduxAction

controllers/counter/counter.controller.ts

import { RootState } from "../store";
import { ReduxController, ReduxControllerBase, ReduxAsyncAction, CommitFunction, ReduxAction, AutoUnsubscribe, ReduxEffect, ReduxWatch } from "redux-controllers";


export interface CounterState {
    counter: number,
}

@ReduxController((rootState: RootState) => rootState.counterState)
export class CounterController extends ReduxControllerBase<CounterState, RootState> {

    defaultState = {
        counter: 0
    }

    @ReduxAction()
    increaseCounter(increaseBy?: number) {
        this.state.counter++;
    }

}

Things to notice

  • All ReduxAction() functions take 1st parameter as the payload
  • increase method is decorated by @ReduxAction(). This decorator converts the method into a Redux Action and creates a reducer in the background
  • State is mutable directly. you don't need to do state = Object.assign({},counter:state.counter++);
  • You don't need to do return any value.

Call your first action

import * as React from 'react';
import { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TextInput } from 'react-native';
import { initStore, RootState } from './controllers/store';
import { GetController, ReduxConnect, Connect } from 'redux-controllers';
import { CounterController } from './controllers/counter/counter.controller';
import Counter, { CounterConnectedProps } from './counter.component';


// Initiate Redux Stores
initStore();
export default class App extends Component<AppProps, AppState> {

  increment = () => GetController(CounterController).increaseCounter();

  counterConnector = (state: RootState): CounterConnectedProps => ({
    counter: state.counterState.counter,
  })

  render() {
    return (
      <View style={styles.container}>
        <Connect connector={this.counterConnector}>
          <Counter />
        </Connect>
        <TouchableOpacity onPress={this.increment} style={styles.button}>
          <Text style={styles.buttonText}>Increase ++ </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

export interface AppProps {
  counter: number;
}

export interface AppState {
  counter: number;
}


const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'stretch',
    paddingTop: 60,
  },
  counterText: {
    flex: 1,
    color: '#222',
    fontFamily: 'Arial',
    fontSize: 20,
    textAlign: 'left',
  },
  button: {
    backgroundColor: '#333',
    padding: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontFamily: 'Arial',
    fontSize: 16,
    textAlign: 'center',
  }
});

Things to notice

  • To get the instance of the controller you can use GetController function.
  • There are multiple ways of reading the state
    • Read State Directly
      • Eg: `GetController(CounterController).state.counter
    • Subscribe to state
      • Eg:
      GetController(CounterController).subscribeTo(state=>state.counter).subscribe(counter=>{
        ...
      })
    • For React and React Native Projects, there are two helper functions that are available to connect them directly
      • Using @ReduxConnect Decorator

        @ReduxConnect<RootState, any>((rootState) => ({
            counter: rootState.counterState.counter,
        }))
        • @ReduxConnect decorater will automatically push state changes to the prop of components

        • To get the typings, you should provide interface of RootState and the interface the properties of the component being decorated

        • The object returned by the function is pushed as properties whenever any changes happen to the mapped states.

      • Using HOC Connect inside template

        • Connect accepts a property connector which is a state mapping function

Advanced Features and Configurations

  1. Redux Async Actions
  2. Redux Watch
  3. Redux Effects
  4. Connecting Existing Middleware
  5. Configuring Persistence
  6. Enabling/Disabling Redux Debugger
  7. Accessing Root State within controllers
  8. Omitting sub states from persisting
  9. Named/Unnamed Actions
  10. Customized Commit Messages
  11. Providers
  12. Resetting SubStates and RootState
  13. Writing Helper Functions in Redux Controller
  14. State based watchers
Todo: Write a doc for Advanced Features and Configurations

Redux Async Actions

Redux Async Actions helps you to work with asynchronous state changes

Eg: counter.controller.ts

import { RootState } from "../store";
import { ReduxController, ReduxControllerBase, ReduxAsyncAction, CommitFunction, ReduxAction, AutoUnsubscribe, ReduxEffect, ReduxWatch } from "redux-controllers";


export interface CounterState {
    counter: number,
}

@ReduxController((rootState: RootState) => rootState.counterState)
export class CounterController extends ReduxControllerBase<CounterState, RootState> {

    defaultState = {
        counter: 0
    }

    @ReduxAsyncAction()
    async loadCounterFromBackend() {
        const counterValue: number = await new Promise(resolve => {
            setTimeout(() => {
                resolve(100);
            }, 2000);
        });

        this.commit(state => {
            state.counter = counterValue;
        });
    }

}

app.tsx

...

export default class App extends Component<AppProps, AppState> {

  loadRemoteState = () => GetController(CounterController).loadCounterFromBackend();

  counterConnector = (state: RootState): CounterConnectedProps => ({
    counter: state.counterState.counter,
  })


  render() {
    return (
      <View style={styles.container}>
        <Connect connector={this.counterConnector}>
          <Counter />
        </Connect>
        <TouchableOpacity onPress={this.loadRemoteState} style={styles.button}>
          <Text style={styles.buttonText}>Load State </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

...

Things to notice

  • Once an asynchronous action is completed, this.commit is called to make the state changes
  • There will be two Redux Action Fired
    • One when the method is called
    • One when the commit happens
  • The method is a promise and the promise gets resolved when the commit happens. This helps you to maintain local state of the action within the component

Redux Watch

Redux Watch helps you to watch a field/ path/ computed value and trigger a function based on on it

    @ReduxWatch((rootState: RootState) => ({
        token: rootState.user.accessToken,
    }))
    watchUserToken({token}: { token: string }) {
       // Do something
       // Eg: Configure Service SDK
    }

Things to notice

  • @ReduxWatch decorator takes in a state mapping function as the only parameter
  • The decorated method will only fire if the returned object's key value has changed
  • The mapping function must always be an object. It should not be a primitive value

Redux Effects

Redux Effect helps you to trigger a function as a side effect when a Redux Action is dispatched.

    @ReduxEffect('LOGIN')
    async onUserLoggedIn({user}: { user: any }) {
       // Do something
       // Eg: Configure Service SDK
    }

Things to notice

  • @ReduxEffect decorator takes in a the action name to watch for as the only parameter
  • The decorated method will only fire if the mentioned action is dispatched
  • The method decorated by @ReduxEffect must always be a promise.

State Providers

State providers helps you to easily manage resources and entities in the state with caching yyy.controller.ts

    defaultState = {
        todoList: ProvidedState([]),
        todoMap: {},
        timeBasedList: ProvidedTimeBasedState([])
    }

    providers = {
        state: {
            todoList: Provider(async () => {
                await new Promise((res, rej) => {
                    setTimeout(() => {
                        res();
                    }, 2000);
                });
                return dummyTodos;
            }, 2000),
            todoMap: ProvideKey(async (key) => {
                await new Promise((res, rej) => {
                    setTimeout(() => {
                        res();
                    }, 2000);
                });
                return {
                    id: key,
                    text: key + "Todo 1",
                    isCompleted: false
                };
            }),
            timeBasedList: ProvideTimeRangeBasedData<Todo[]>((range) => this.loadTodosInTimeRange(range), true)
        },
        cacheTimeout: 0
    }

yyy.component.tsx

// load resources | respects cache
await GetController(YYYController).load(state=>state.todoList);

// load resource | force refresh
await GetController(YYYController).load(state=>state.todoList,true);

// Loading Key
await GetController(YYYController).load(state=>state.todoMap.xyz);

// Loading Resources belonging to a date range
await GetController(YYYController).load(state=>state.timeBasedList,{ from: new Date().getTime(), to: new Date().getTime() - 100000 });

Todo: Write a doc for Advanced Features and Configurations

Complex Store Examples

  1. Calendar Events
  2. Paginated List
  3. UI Elements State
Todo: Write a doc for Complex Store Examples

Simple Testing Framework

Todo: Write Doc for Simple Testing Framework

Contributors

Todo: Fill in contributors

Debugging in NodeJs

Run Process chrome://inspect/#devices Install Remove Dev npm install --save-dev remotedev-server or npm install -g remotedev-server Run to start dev server remotedev --hostname=localhost --port=1234

Pass in options.devToolsOptions to init function

            devToolsOptions: {
                host: '127.0.0.1',
                port: 1234
            }

Todo

Add Helper Notes with https://stackoverflow.com/questions/43177855/how-to-create-a-deep-proxy for state and commit function

  • Add .set(state=>state.foo,{value}); method to set a path
  • Add Provide Crud | Provide Resource
ProvideCrud({
load:()=>{},
edit:()=>{}
})
  • Add Provide Resources
ProvideResources({
key:'id'
load:()=>{},
edit:()=>{},
delete:()=>{}
}),
  • make payload optional for redux action and async action
  • expose this.rootState

Utopian State Management Library

https://medium.com/@ragularuban/thought-share-my-ideal-state-management-for-angular-react-vue-and-what-not-cab7725e2c16