Skip to content

API

Reapex API simplified the React & Redux application.

App: Class

The constructer function of App accepts an optional argument config: AppConfig.

const config: AppConfig = { middlewares }
const app = new App(config)
Interface AppConfig
interface AppConfig {
  middlewares?: Redux.Middleware[]
}
NameTypeDescriptionOptional
middlewaresstringAn array of Redux middleware.
Note: Reapex has redux-saga middleware by default.
Yes

model: <T extends Record<string, any>>(namespace: string, initialState: T) => Model

Calling app.model() to create a model. A model has its own namespace which should be unique in the application. The initialState is a key-value object.

const model = app.model('LoginForm', { username: '', password: '' })
Interface Model
interface Model {
  mutations: (mutationMap: P, subscriptions?: S) => [ActionCreators, ActionTypes]
  selectors: Selectors<T>
  effects: (effectMap: P, triggerMap?: S) => [ActionCreators, ActionTypes]
}

mutations: (mutationMap: P, subscriptions?: S) => [ActionCreators, ActionTypes]

The mutations describes how the model data will be updated. The mutations function accepts an object as the first parameter which key describe the operation and value is a function that describes the mutation input and output.

Note: once the initialValue of a model is properly typed, you will get state as a strong-typed Immutable Record.

The model.mutations() returns a tuple: [actionCreators, actionTypes]. The actionCreators is an object which has the same keys as the object passed into mutations() and the values are strong-typed action creators.

The actionTypes is an object which has the same keys as the object passed into mutations() but values are strong-typed action types.

const [actionCreators, actionTypes] = model.mutations({
  setUserName: (username: string) => state => state.set('username', username),
  setPassword: (password: string) => state => state.set('password', password),
})

The generated actionCreators:

actionCreators = {
  setUserName: (username: string) => ({ type: 'LoginForm/setUserName', payload: [username] }),
  setPassword: (password: string) => ({ type: 'LoginForm/setPassword', payload: [password] }),
}

The second argument of mutations() function is used to subscribe to external actions. For example, actions of other model. It has the same type definition of mutationMap but the difference is the keys are the actual action types.

const [actionCreators, actionTypes] = model.mutations({
  setUserName: (username: string) => state => state.set('username', username),
  setPassword: (password: string) => state => state.set('password', password),
}, {
  [managerActionTypes.reset]: () => state => state.set('password', '').set('username', '')
})

selectors: Selectors<T>

Pre-defined redux selectors for each field. For convenience, Reapex will create selectors for all the fields of the state.

const username = model.selectors.username(state)
const password = model.selectors.password(state)

The selectors can be used later with react-redux connect() function or with hooks

import { useSelector } from 'react-redux'

const username = useSelector(UserInfoModel.selectors.username)
const password = useSelector(UserInfoModel.selectors.password)

effects:(effectMap: P, triggers?: S) => [ActionCreators, ActionTypes]

The effects define application side effects, which are basically sagas of redux-saga.

The model.effects() provides a simplified and structured way of creating sagas. For example, if we want to make an API call to validate the username when it changes, we can simply do:

model.effects({
  setUserName: {
    *takeEvery(action: ReturnType<typeof actionCreators.setUserName>) {
      const [username] = action.payload
      yield call(api.validate, username)
    }
  }
})

The above saga is run on every username change because it runs takeEvery effects. Apart from takeEvery effect, Reapex supports takeLatest, debounce, throttle effects as well. Please refer to: redux saga documentation for more details about different effects.

If you don't want to call the validation API every time username changed, because setUserName maybe called every time when user type. Running debounce effect maybe more reasonable.

model.effects({
  setUserName: {
    *debounce(action: ReturnType<typeof actionCreators.setUserName>) {
      const [username] = action.payload
      yield call(api.validate, username)
    },
    ms: 2000,
  }
})

Note: debounce and throttle effect has an extra configure field ms.

There are scenarios that one action doesn't mutate the state directly but only triggers side effects. For example, click login button doesn't update the state but just make an API call to authenticate the user.

The model.effects() function accepts argument which we call it triggers, we can define action creators which only trigger sagas.

const [effectActionCreators, effectActionTypes] = model.effects({
  setUserName: {
    *debounce(action: ReturnType<typeof actionCreators.setUserName>) {
      const [username] = action.payload
      yield call(api.validate, username)
    },
    ms: 2000,
  }
}, {
  submit: {
    *takeLatest() {
      const username = yield select(model.selectors.username)
      const password = yield select(model.selectors.password)
      yield call(api.login, username, password)
    }
  }
})

The above example defines a submit action. It will call api.login when it's triggerred.


createStore: () => Redux.Store

Create Redux store which will be passed to <Provider>.

const store = app.createStore()
render(
  <Provider store={store}>
    <LoginForm />
  </Provider>,
  rootElement,
)

runSaga: (saga: Saga) => void

This is useful if you were adopting Reapex to an existing Redux application which uses redux-saga middleware. app.runSaga(saga) function allows to run saga directly without defining model and effects, for example, migrating existing sagas to use Reapex.


use: (plugin: Reapex.Plugin) => any

Applying Reapex plugin with app.use(plugin).

A plugin in Reapex is a reusable model which is sharable and can be published to NPM. For example, reapex-plugin-dataloader.


Plugin: (app: App, ...args: any[]) => any

The Reapex plugin provides an unified and easy way of writing sharable Redux code. For example, we can wrap the LoginForm logic and provides it as a Reapex Plugin.

function loginPlugin(appInstance: App, namespace: string) {
  const model = appInstance.model('LoginForm', { username: '', password: '' })
  const [actionCreators] = model.mutations({
    setUserName: (username: string) => state => state.set('username', username),
    setPassword: (password: string) => state => state.set('password', password),
  })
  const [effectActionCreators] = model.effects(
    {
      setUserName: {
        *debounce(action: ReturnType<typeof actionCreators.setUserName>) {
          const [username] = action.payload
          yield call(console.log, username)
        },
        ms: 2000,
      },
    },
    {
      submit: {
        *takeLatest() {
          const username = yield select(model.selectors.username)
          const password = yield select(model.selectors.password)
          yield call(console.log, username, password)
        },
      },
    },
  )
  return {
    model,
    actionCreators,
    effectActionCreators,
  }
}

const { model, actionCreators, effectActionCreators} = app.use(loginPlugin, 'LoginForm')
export const LoginForm = () => {
  const username = useSelector(model.selectors.username)
  const password = useSelector(model.selectors.password)
  const dispatch = useDispatch()

  return (
    <>
      <div>
        User Name:
        <input type="text" value={username} onChange={e => dispatch(actionCreators.setUserName(e.target.value))} />
      </div>
      <div>
        Age:
        <input type="password" value={password} onChange={e => dispatch(actionCreators.setPassword(e.target.value))} />
      </div>
      <button onClick={() => dispatch(effectActionCreators.submit())}>Submit</button>
    </>
  )
}