0.0.2 • Published 5 years ago

redux-flow-mapper v0.0.2

Weekly downloads
4
License
MIT
Repository
github
Last release
5 years ago

Redux Flow Mapper

React/Redux utility to create flows that acts as Actions and Reducers with Typescript and Decorators.

NPM

NPM

Work in Progress:

  • Core functionalities: done
  • Actions dispatchers: done
  • Reducer handlers: done
  • Triggers handlers: done
  • Promise based events: done
  • Http-Promise based events: done
  • Tests: TBD

Requirements

# requires the following packages:
npm i --save react-redux
npm i --save rxjs
npm i --save axios
npm i --save-dev @babel/plugin-proposal-decorators
npm i --save-dev @babel/plugin-proposal-class-properties

Install

# install Flow Mapper
npm i redux-flow-mapper

Setup tsconfig and babelrc

Include support for decorators in the tsconfig.json file:

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

If Babel is used to transpile the application, include the plugins below in the .babelrc file. Consider these itens as firts in the plugins list and in the same order that appears below:

"plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true}],
    ...
  ],

Concept

This package is based in the common flow principles to control the activities in the frontend. Any action of flow is build on FlowMapper and React just render the results.

The base flow of FlowMapper starts with flow actions and states, are intercepted by triggers, encapsulated with modules, created by mapper and distributed thru connections: Flow Chart Concept

Redux by Class and Decorators

All Redux logics were designed to be a decorated classes and methods.

FlowState

The development starts with states, that is the initial data for reducers.

import {FlowState} from 'redux-flow-mapper';

@FlowState({
  name: 'name-of-your-state'
})
export class MyState {
  myCustomData = '';
  ...
}

The name in the decorator, will be used as reducer name in redux store.

FlowActions

Actions are designed to be dispatchers and reducers. Any method decored by getState will be replaced by dispatcher function that emits an action with the name of method as type and the arguments as args in the dispatch.

import { FlowActions, getState } from "redux-flow-mapper";

@FlowActions({
  // name of the flow
  // all methods in this class will use this name
  // as prefix in the redux action
  name: "name-of-your-action-flow"
})
export class MyActions {
  @getState(MyState)
  start = () => (state: MyState): MyState => {
    return { ...state, myCustomData: "something" };
  };
}

FlowTriggers

Triggers are interceptors that listen to action completion and returns the previous and new changed state.

And must be used to trigger events from another actions.

This trigger can't be used to listen to event in the same class that hold the trigger function.

import {FlowActions, getTrigger} from 'redux-flow-mapper';

@FlowActions({
  name: 'name-of-another-action'
})
export class MyAnotherActions {
  ...
  @getAction(MyActions) actions?: MyActions;

  @getTrigger(MyState, MyActions, 'start')
  checkIfActionIfStarted = (
    previous: MyState,
    state: MyState
  ) => {
    if (!previous.myCustomData && state.myCustomData) {
      // make your actions
      ...
    }
  };

  ...
}

FlowModules

Modules are encapsulation that holds all flow states and actions.

import { FlowModule } from "redux-flow-mapper";

@FlowModule({
  states: [MyState],
  actions: [MyActions, MyAnotherActions]
})
export class MyModule {}

FlowMapper

The FlowMapper is the main class and instantiate all actions and states, transforming then in dispatchers and reducers.

import { FlowMapper } from "redux-flow-mapper";

export const myMapper = new FlowMapper({
  devExtension: true, // enables redux-dev-extension for chrome
  modules: [MyModule]
});

Connecting in React

Use Redux Provider and connect it to the mapper store:

import { Provider } from 'react-redux';
import * as React from 'react';

...

export class App extends React.Component {
  public render() {
    return (
      <Provider store={myMapper.createStore()}>
        ...
      </Provider>
    );
  }
}

The function createStore instantiate a Store object, that can be used in the redux Provider.

Render in component

Use FlowConnection to replace connect from Redux. This decorator automatically connect the component to the redux store and instantiate actions and states from mapper.

import { FlowConnection, getAction, getState } from "redux-flow-mapper";

class Props {
  // decorate property with action and send the action
  // object to the component thru props
  @getAction(MyActions) actions?: MyActions;

  // decorate property with state and send the action
  // object to the component thru props
  @getState(MyState) stateContent?: MyState;
}

@FlowConnection({
  flow: myMapper, // <-- set the instance of FlowMapper
  props: Props // <-- set to the Props class
})
// always use the Props class to incorporate the React component
export class MyComponent extends React.Component<Props> {
  render() {
    return (
      <div>
        <strong>Test Component</strong>
        <p>custom data = {this.props.stateContent.myCustomData}</p>
        <p>
          <button onClick={this.start}>Start</button>
        </p>
      </div>
    );
  }
  start = () => {
    this.props.actions.start();
  };
}

Tools

FlowPromised

These Actions are designed to be a Promise based template. The FlowMapper will generate the action dispatchers as loading, completed and failed. The promise handle function will be replaced by dispatcher and the Promise will output the methods above for each Promise event.

import {
  FlowPromised,
  FlowPromiseActions,
  FlowPromiseState
} from "redux-flow-mapper";

@FlowState({
  name: "my-promised-state"
})
export class MyState extends FlowPromiseState<any, any> {}

@FlowPromised({
  name: "my-promised-action",
  state: MyState
})
export class MyPromisedAction implements FlowPromiseActions {
  // pass the Promise inside the start function
  // dont use arrow function or the decorator will not found
  // the method
  start(request: string, error: string) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (error) {
          reject({ error });
        } else {
          resolve({ request });
        }
      }, 2500);
    });
  }
}

Also, the promised methods can be triggered in another actions, by using the completed and failed names:

import { FlowActions, getTrigger } from "redux-flow-mapper";

@FlowActions({
  name: "my-promised-catcher-action"
})
export class MyPromisedCatcherAction {
  @getAction(MyActions) actions?: MyActions;

  @getTrigger(MyState, MyPromisedAction, "completed")
  checkIsComplete = (previous: MyState, state: MyState) => {
    if (!previous.isCompleted && state.isCompleted) {
      console.log("isCompleted ok");
      actions.start();
    }
  };

  @getTrigger(MyState, MyPromisedAction, "failed")
  checkIsFailed = (previous: MyState, state: MyState) => {
    if (!previous.isFailed && state.isFailed) {
      console.log("isFailed ok");
    }
  };
}

FlowHttpRequest

These Actions are designed to be a Http-Promise based template. Use Axios as http requester and works same as FlowPromised

import {
  FlowHttpRequest,
  FlowHttpActions,
  FlowHttpState,
  http // <-- return an Axios object that makes requests
} from "redux-flow-mapper";

@FlowState({
  name: "testhttp"
})
export class TestHttpState extends FlowHttpState<any> {}

@FlowHttpRequest({
  name: "test.http.actions",
  state: TestHttpState
})
export class TestHttpActions implements FlowHttpActions {
  request(simulateError?: number) {
    if (simulateError === 400) {
      return http.get("http://localhost:9999/fail400");
    }
    if (simulateError === 999) {
      return http.get("http://server-does-not-exists");
    }
    return http.get("http://localhost:9999");
  }
}

The FlowHttpState is based in the same event handler FlowPromised (loading, completed and failed), bu the response returns and AxiosResponse or AxiosError depending the handler.

The object handling can be defined as example below. All Axios responses will be stored in the properties response or error.

class StageHttpComponentProps {
  @getAction(TestHttpActions) actions?: TestHttpActions;
  @getState(TestHttpState) stage?: TestHttpState;
}
@FlowConnection({
  flow: reduxflowApplication,
  props: StageHttpComponentProps
})
export class StageHttpComponent extends React.Component<
  StageHttpComponentProps
> {
  render() {
    return (
      <div className="flow-box">
        <p>Based on FlowActionsHttpRequest</p>
        <p>
          <button onClick={this.request}>request</button>
          <button onClick={this.request400}>error 400</button>
          <button onClick={this.request999}>error 999</button>
        </p>
        {this.renderResponse()}
        {this.renderError()}
      </div>
    );
  }

  renderResponse() {
    const { isCompleted, response } = this.props.stage;
    if (isCompleted) {
      return (
        <div>
          <p>Response OK</p>
          <p>Status = {response.status}</p>
          <p>Data = {JSON.stringify(response.data)}</p>
        </div>
      );
    }
    return null;
  }

  renderError() {
    const { isFailed, error } = this.props.stage;
    if (isFailed) {
      if (error.response) {
        return (
          <div>
            <p>Response NOK</p>
            <p>Status = {error.response.status}</p>
            <p>Error = {JSON.stringify(error.response.data)}</p>
          </div>
        );
      }
      return (
        <div>
          <p>Response Can´t be Reached</p>
          <p>Fail to request on {error.config.url}</p>
        </div>
      );
    }
    return null;
  }

  request = () => {
    this.props.actions.request();
  };

  request400 = () => {
    this.props.actions.request(400);
  };

  request999 = () => {
    this.props.actions.request(999);
  };
}

Sample

The sample project is available in the source https://github.com/debersonpaula/redux-flow-mapper. Just install dependencies and run with npm start.

License

MIT

sample :https://github.com/debersonpaula/redux-flow-mapper/tree/master/src