- The App - Every app has a Mothership
- Features - Your fleet of features flies in formation
RematchModel
- encapsulates redux logic for a featureFlyingSaucerView
- encapsulates how a feature responds to the Mothership's history- enhanced routing - go where you want to go
context
bindingsredux
bindings
Every application starts with a Mothership
at the root to control its fleet.
Sets up the environment, optionally using the passed app
.
Top-level features should be mounted as children of a Mothership
.
props:
(Optional) app: FlyingSaucerApp
example:
<Mothership>
<BrandChrome>
<FeatureOne />
<FeatureTwo path="/two" />
</BrandChrome>
</Mothership>
Create a new container for singletons used by Mothership.
arguments:
config: {
(Optional) inject: {}
(Optional) rematch?: RematchConfig
}
returns:
FlyingSaucerApp
example:
createApp({ inject: { api } })
react-mothership
is based around feature modules. The tree created by the react components at the root of your application describes the active features and how they are wired together - or in better words, what formation your fleet is flying in.
Decorate a React component at the root of a feature module. When mounted, it registers its dependencies with the context. Acts as a boundary for errors and suspense.
Want lazy loading? Use a React.lazy component!
arguments:
(Optional) config: {
(Optional) name: string, // display name
(Optional) placeholder: React.Element, // react element to show while suspended,
(Optional) recovery: React.Element, // react element to show when suspense fails
(Optional) models: [Rematch.Model], // rematch models to add to the store dynamically
(Optional) views: [FlyingSaucerView], // views to provide in the parent scope
(Optional) provides: {}, // concrete ambient dependencies
}
returns
FeatureComponent
example:
import Base from './Base'
import views from './views'
import models from './models'
const package = createFeature({
views,
models,
})
export default Base |> package
Behaves almost like a feature flag; mount this inside your mothership.
props:
(Optional) path: string
example:
import Feature from '@/features/Feature'
<Mothership>
<Feature path="/somewhere" />
</Mothership>
Models encapsulate state logic.
When first mounted, a model will be registered with the store and built. Each of its factory functions will receive the inject
property of the current Mothership
.
props:
{
name: string, // name to register globally
(Optional) reducers: {
[actionName]: (state, payload) => state
}
(Optional) selectors: (slice, createSelector, hasProps, inject) => {
[selectorName]: models => (state, payload) => any
}
(Optional) effects: (dispatch, inject) => {
[actionName]: async (state, payload) => any
}
}
Views are declarative responses to app.history
changes.
Each view will become a <Route />
in a <Switch />
component
can be aReact.lazy
component and always takes precedencerender
,effect
, andredirect
can be combined for a side-effect in response to routing.
props:
{
name: string
path: string
(Optional) resolve: (params: {}) => string // map params to formatted path
(Optional) component: React.Component
(Optional) render: (props: {}) => React.Component
(Optional) effect: (dispatch: RematchDispatch) => any,
(Optional) redirect: string | { to, ...params }
}
Every routing primitive from react-router
is available from react-mothership
. Any components that take a to
prop can also resolve it from the current scope's views
.
example:
import { Switch, Route, Redirect, Link, NavLink } from '@@'
function Nope({ match }) {
return <Redirect view="nope" params={match} />
}
props:
view: string
params: any
todo:
You may notice we export components from react-router-dom
; this might be revisited to use some form of DI
Hook to retrieve the mothership's app configuration.
returns:
FlyingSaucerApp
example:
const app = useApp()
Hook to retrieve the current scopes's ambient dependencies.
arguments:
paths: string[]
returns:
any[]
throws:
NotProvidedInScopeError
example:
const [Layout, appName] = useProvided('Layout', 'config.app.name')
alias: $$
react-redux
's connect
function that retrieves the Mothership
's selectors instead of directly mapping state.
arguments:
mapSelect: RematchSelect => {}
mapDispatch: RematchDispatch => {}
example:
const enhance = $$(
select => ({
todos: select.todos.list,
}),
dispatch => ({
addTodo: dispatch.todos.addTodo,
})
)
alias: _$
connect
s only dispatchers. $$
but skips the first argument.
arguments:
mapDispatch: RematchDispatch => {}
example:
const enhance = _$(dispatch => ({
addTodo: dispatch.todos.addTodo,
}))
Hook to run dispatchers as a side-effect when any value in watch changes.
arguments:
effectWithDispatch: RematchDispatch => any
watch: any[]
example:
useAppEffect(dispatch => dispatch.storage.retrieve(props.id), [props.id])
Hook to pick a selector from the current store and run it. The ref to mapSelect
will change when deps
changes. Mapped selectors will be passed payload
as ownProps
.
arguments:
mapSelect: RematchSelect => any
payload: any
deps: [any]
example:
const todos = useAppSelector(it.todos.list)