All Projects → terotests → Ts2redux

terotests / Ts2redux

Licence: mit
Compile standard TypeScript classes to Redux or React Context API

Programming Languages

javascript
184084 projects - #8 most used programming language
typescript
32286 projects

Projects that are alternatives of or similar to Ts2redux

Egg React Webpack Boilerplate
Egg React Server Side Render(SSR) / Client Sider Render(CSR)
Stars: ✭ 634 (+1525.64%)
Mutual labels:  react-redux
Thinkful Workshop React Redux Node Mongodb Webpack2
Stars: ✭ 12 (-69.23%)
Mutual labels:  react-redux
Emptyd Admin Webpack
基于typescript react webpack的脚手架
Stars: ✭ 30 (-23.08%)
Mutual labels:  react-redux
React Cnode
基于webpack + react + react-router + redux + less + flex.css + ES6 的React版cnode社区
Stars: ✭ 688 (+1664.1%)
Mutual labels:  react-redux
Todo React Redux
Todo app with Create-React-App • React-Redux • Firebase • OAuth
Stars: ✭ 942 (+2315.38%)
Mutual labels:  react-redux
Create React App Redux
React Router, Redux, Redux Thunk & Create React App boilerplate
Stars: ✭ 885 (+2169.23%)
Mutual labels:  react-redux
Manta
🎉 Flexible invoicing desktop app with beautiful & customizable templates.
Stars: ✭ 5,160 (+13130.77%)
Mutual labels:  react-redux
Hellobooks
A Single-Page Library Management App built with nodejs, express and react and redux
Stars: ✭ 37 (-5.13%)
Mutual labels:  react-redux
React Native App Boilerplate
A simple and scalable boiler plate code for React Native App using React Native Navigation by WiX and Saga .
Stars: ✭ 9 (-76.92%)
Mutual labels:  react-redux
React Retirement Calculator
A dynamically updating retirement calculator.
Stars: ✭ 29 (-25.64%)
Mutual labels:  react-redux
React Redux Boilerplate
A minimal React-Redux boilerplate with all the best practices
Stars: ✭ 799 (+1948.72%)
Mutual labels:  react-redux
React email editor
This project is experimental! It's my attempt to create visual email template editor using React+Redux+etc... tools stack.
Stars: ✭ 19 (-51.28%)
Mutual labels:  react-redux
Reactspa
combination of react teconology stack
Stars: ✭ 911 (+2235.9%)
Mutual labels:  react-redux
Soundcloud Redux
SoundCloud API client with React • Redux • Redux-Saga
Stars: ✭ 681 (+1646.15%)
Mutual labels:  react-redux
Generator Jam3
This is a generator for Jam3 projects
Stars: ✭ 34 (-12.82%)
Mutual labels:  react-redux
Graphqldesigner.com
A developer web-app tool to rapidly prototype a full stack implementation of GraphQL with React.
Stars: ✭ 587 (+1405.13%)
Mutual labels:  react-redux
Fascia
Stars: ✭ 12 (-69.23%)
Mutual labels:  react-redux
Redux Micro Frontend
This is a library for using Redux to manage state for self-contained apps in a Micro-Frontend architecture. Each self-contained isolated app can have its own isolated and decoupled Redux store. The componentized stores interact with a global store for enabling cross-application communication.
Stars: ✭ 38 (-2.56%)
Mutual labels:  react-redux
Taliesin
Lightweight audio streaming server
Stars: ✭ 35 (-10.26%)
Mutual labels:  react-redux
Iguazu Rest
✨ Iguazu REST is a plugin for the Iguazu ecosystem that allows for pre-built async calls for REST with smart caching.
Stars: ✭ 21 (-46.15%)
Mutual labels:  react-redux

TypeScript to Redux

Compile simple TypeScript classes into both Redux or React Context API state machines, which work together with Redux Devtools (yes, also React Context changes can be viewed from Redux Devtools, time travel also works! 🎉)

Too good to be true?

Yes, it is true, but the compiler is still quite young and Please Do check the Limitations before you test the library.

Installation

npm i -g ts2redux

Run test app by cloning Repository and then

npm install
npm test

Why?

It is not likely that state management gets much easier than this:

  1. State is written using as TypeScript class - initializers, reducers, actions are derived from that
  2. You can choose Redux or Context API (or both for that matter)
  3. Partial Redux Devtools support for React Context API
  4. Just use normal async - no fancy library needed for async operations
  5. Typed with TypeScript

Also the library imposes no direct dependencies, after it has compiled the sources, you do not need the compiler any more - the resulting files have not dependencies to anything else than proven libraries like React, Immer etc.

And as an added bonus, we get selector support with reselect using method getters!

Acknowledgements

This library would not have been possible without following great OS tools:

Also inspiration sources were fellow coders at Koodiklinikka, developers at Leonidas and several blog article writers 12

Introduction

The simplest way of writing a stateful model is simply creating a simple TypeScript class would be like this

export class SimpleModel {
  items: any[] = [];
  async getItems() {
    // get some data from the internet
    this.items = (await axios.get(
      "https://jsonplaceholder.typicode.com/todos"
    )).data;
  }
}

Or if you prefer the classical increment / decrement example

export class IncModel {
  cnt: number = 0;
  increment() {
    this.cnt++;
  }
  decrement() {
    this.cnt--;
  }
}

The question asked was: would it be possible to transfer this simple state representation automatically to Redux? Or even to React Context API?

Turns out with a little bit of compiler magic we can transform the idea of the class into both Redux and React Context API representations. The Redux compiling will create necessary Actions, Enumerations and Reducers, Combined Reducers and MapStateToProps, MapDispatchToProps to manage the component state correctly.

The compiler does not need much help, we need to add the JSDoc comment property before the class and also we need to remember to give types to the properties of the class.

/**
 * @redux true
 */
export class IncModel {
  // ...
}

Then we can compile the model

  ts2redux <path>

And the directory will have reducers/ directory where IncModel and SimpleModel are defined IncModel.tsx and
SimpleModel.tsx together with all Redux ceremony and more.

Custom combineReducers

The generated index.ts will export reducerObject with all the parameters required for combineReducers

Using with React Connected Router

Use the exported reducerObject from index.ts as parameter to combineReducers and follow the installation instructions from the Connected React Router.

If you want to user router from the model itself use Generic Dispatcher described below.

Generic Dispatcher

Generic dispatch is available to async functions. To create a generic dispatcher you can add JSDoc comment @dispatch true

  /**
   * @dispatch true
   * @param action
   */
  async MyDispatcher(action: any) {
    // you can give any dispatcher action as parameter to this function
  }

Sometimes you want to create a generic dispatcher, for example when you want to connect your state to Connected React Router you should call it something like this

this.MyDispatcher(push("/path/to/somewhere"));

Original example from Connected React Router documentation.

Private functions

Private functions and functions which return value are not compiled as reducers

/**
 * @redux true
 */
export class MyModel {
  // will not be compiled as reducer
  private someCalculation(value) {}
  // will not be compiled as reducer
  someCalculation(value): number {
    return 100;
  }
}

Limitations

Async Functions can not mutate state deeply (synchronous can)

If you want to mutate state deeply from async function you must call first syncronous function.

async function can read state but can only assign (=) to class properties, which generates a dispatch. Do not mutate state deeply in asyncronous functions, that will not work and will generate error

// this is OK
this.items = [];
// this is error, no dispatch generated, Redux will complain about this too
this.items.sort(/*... */);

Functions can only have one parameter

class Foo {
  // OK
  hello(message: { sender: string; receiver: string }) {}
  // this is ERROR
  hello(sender: string, receiver: string) {}
}

The reason for this is just simplicity: the first parameter is compiled directly to the actions payload. In the future the compiler might compile functions with variable number of parameters directly to the payload, but this is not supported at the moment.

React Context API -components are not removed from Redux Devtools after unmount

If you generate a lot of Redux Context API -components and Redux Devtools is enabled, history of unmounted components is visible in the Redux Devtools debugging history. In some cases this may be desirable, in some cases not.

In case the component is unmounted, it's listeners are unsubscribed and time travel will not work.

Using React Context API

// for the new ReactContext API
import { IncModelConsumer, IncModelProvider } from "./models/reducers/IncModel";

For React Context API we simply create a upper level <model>Provider and lower in the VDOM tree use <model>Consumer to render components or to call methods of the model.

<IncModelProvider>
  <IncModelConsumer>
    {state => (
      <div>
        <div>{state.cnt}</div>
        <button onClick={state.increment}>+</button>
        <button onClick={state.decrement}>-</button>
      </div>
    )}
  </IncModelConsumer>
</IncModelProvider>

Using Redux

For Redux the compiler generates the main reducer import in

import { reducers } from "./models/reducers/";

This is pretty standard Redux stuff, the main reducer is given then to the createStore

let store = createStore(reducers /** other params*/);

After which you create <Provider store={store}> normally.

The IncModel -component would look like this:

// impor the IncModel
import * as container from "../models/reducers/IncModel";

// abstract properties version of the component
export interface Props extends container.Props {}

// this component can be re-used
export const AbstractInc = (props: Props) => {
  return (
    <div>
      <div>{props.cnt}</div>
      <button onClick={props.increment}>+</button>
      <button onClick={props.decrement}>-</button>
    </div>
  );
};
// Connect the abstract component to the Redux model
export const ReduxInc = container.StateConnector(AbstractInc);

Selectors

Selectors are great, if you want to avoid expensive recalculations and optimize rendering performance using PureComponents.

To create a selector, define function with get -modifier like get someProperty() : someReturnValueType. This will create a new property someProperty which can be used as a cached result of some computation based on the model.

For example see code from TodoList.ts

export class TodoList {

  // ... some model parameters used to transform the list...
  items: TodoListItem[] = []
  sortOrder:SortOrder = SortOrder.ASC
  listStart:number = 0
  listPageLength:number = 10

  // use this like <PureList items={props.listToDisplay}/>
  get listToDisplay() : TodoListItem[] {
    return this.items
      .filter( item => item.completed )
      .sort( sortFn(this.sortOrder) )
      .slice( this.listStart, this.listStart + this.listPageLength)
  }

The advantage of selector is that value is memoized and will only update if parameters affecting it's value will change. In the example above,listToDisplay is recalculated only if the value of items, sortOrder, listStart or listPageLengthchanges.

If property above is given to a PureComponent like this

<PureList items={props.listToDisplay} />

The component will render only when parameters affecting it's computation change.

Examples

Some example of Models are available in src/frontend/models -directory.

Error handling in async functions

In typical Redux code you want to have some kind of loading state

export type TaskState = "UNDEFINED" | "RUNNING" | "LOADED" | "ERROR";

Any kind of loading state is pretty easy to implement, for example

class TodoList {
  items: TodoListItem[] = [];
  state: TaskState = "UNDEFINED";
  async getItems() {
    if (this.state === "RUNNING") return;
    try {
      this.state = "RUNNING";
      this.items = (await axios.get(
        "https://jsonplaceholder.typicode.com/todos"
      )).data;
      this.state = "LOADED";
    } catch (e) {
      this.state = "ERROR";
    }
  }
}

License

MIT.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].