redux-actions-promise-wrapper

Type safe wrapper for redux actions (compatible with redux-saga)

Usage no npm install needed!

<script type="module">
  import reduxActionsPromiseWrapper from 'https://cdn.skypack.dev/redux-actions-promise-wrapper';
</script>

README

Build Status Coverage Status

redux-actions-promise-wrapper

Type safe redux action creator (works with redux saga)

Installation

npm install redux-actions-promise-wrapper --save
yarn add redux-actions-promise-wrapper
bower install redux-actions-promise-wrapper --save

Feature

Create type safe action creators and enable callback for redux saga.

Example Project

https://github.com/thu-san/redux-actions-promise-wrapper-usage-example

Usage

TypeScript

Create Action
import { createAction } from 'redux-actions-promise-wrapper';

const login = createAction('LOGIN/TRIGGER', 'LOGIN/SUCCESS', 'LOGIN/FAILURE')<
  {
    email: string;
    password: string;
  },
  {
    session: string;
  }
>();

login now contains the following properties

login.TRIGGER = 'LOGIN/TRIGGER';
login.SUCCESS = 'LOGIN/SUCCESS';
login.FAILURE = 'LOGIN/FAILURE';

login.trigger(payload) === { type: 'LOGIN/TRIGGER', payload }; // payload must have type { email: string, password: string }
login.success(payload) === { type: 'LOGIN/SUCCESS', payload }; // payload must have type { session: string }
login.failure() ===  { type: 'LOGIN/FAILURE' };

login(payload) === login.trigger(payload);
You can also call on login for trigger action.

Redux Saga Example

Types, Saga and Reducer
// TYPES
interface ILoginTriggerPayload {
  account: string;
}

export const loginAction = createAction('LOGIN/TRIGGER', 'LOGIN/SUCCESS')(
  handleLogin,
  function*(arg: { session: string; account: string }) {
    return undefined;
  }
);
export const logoutAction = createAction('LOGOUT/TRIGGER')<{
  session: string;
}>();

// SAGA
function* handleLogin({ account }: ILoginTriggerPayload) {
  yield delay(1000);
  const putLoginSuccess = put(
    loginAction.success({ session: new Date().toString(), account })
  );
  yield putLoginSuccess;
  return reg(account);
}

export function* authSaga() {
  yield all([takeLatest(loginAction.TRIGGER, loginAction.handleTrigger)]);
}

// REDUCER
interface IAuth {
  account?: string;
  session?: string;
}

const INITIAL_STATE: IAuth = {};

type Actions = ExtractActions<{
  loginAction: typeof loginAction;
  logoutAction: typeof logoutAction;
}>;

const reducer: Reducer<IAuth, Actions> = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case loginAction.SUCCESS: {
      const { account, session } = action.payload;
      return { ...state, account, session };
    }
    case logoutAction.TRIGGER: {
      const { session } = action.payload;
      console.log(`LOGOUT Reducer Session -  ${session}`);
      return {};
    }
    default:
      return state;
  }
};

export default reducer;
Combine Reducer
import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';

import auth, { authSaga } from './auth';

const reducers = combineReducers({
  auth
});

export type AppState = ReturnType<typeof reducers>;

// saga
export const rootSaga = function*() {
  yield all([authSaga()]);
};

export default reducers;
Container
interface IState {
  account: string;
  loading: boolean;
}
type IProp = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps;
export class Dash extends PureComponent<IProp, IState> {
  state: IState = {
    account: '',
    loading: false
  };

  handleLogin = async () => {
    const { loginAction } = this.props;
    const { account } = this.state;
    this.setState({ loading: true });
    const { promise } = loginAction({ account });
    const result = await promise;
    alert(`You are now logged in as ${result}`);
    this.setState({ loading: false });
  };

  handleLogout = async () => {
    const { session, logoutAction } = this.props;
    if (session) {
      logoutAction({ session });
    }
  };

  render() {
    const { session, account: authAccount } = this.props;
    const { account, loading } = this.state;

    const loggedIn = !!session;

    return (
      <div
        style={{
          display: 'flex',
          height: 500,
          background: 'cyan',
          justifyContent: 'center',
          alignItems: 'center'
        }}
      >
        <div>
          <p>Status: {loggedIn ? 'Logged In' : 'Logged Out'}</p>
          <p>Session: {session}</p>
          <p>Email: {authAccount}</p>
          {loading ? (
            '...loading'
          ) : loggedIn ? (
            <button onClick={this.handleLogout}>Logout</button>
          ) : (
            <>
              <p>
                <input
                  type="text"
                  placeholder="account"
                  value={account}
                  onChange={({ target: { value } }) =>
                    this.setState({ account: value })
                  }
                />
              </p>
              <button onClick={this.handleLogin}>Login</button>
            </>
          )}
        </div>
      </div>
    );
  }
}

const mapStateToProps = ({ auth: { account, session } }: AppState) => ({
  account,
  session
});

const mapDispatchToProps = {
  loginAction,
  logoutAction
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Dash);