Client side part of buhoi framework.
Provide loading of stateless functional components depending on route, with support of webpack hot module replacement.
// index.js
const buhoi = require('buhoi-client')
buhoi.start({
createContext: () => require.context('./pages', true, /\.jsx$/),
acceptHotUpdate: module.hot && module.hot.accept,
defaultRoute: '/greetings',
})
// pages/greetings/index.jsx
const { combineReducers } = require('redux')
const { actions: { navigateTo } } = require('buhoi-client')
module.exports = Greetings
module.exports.reducer = combineReducers({ someText: someTextReducer })
function Greetings ({ someText, route, dispatch }) {
return <div>
<h1 onClick={() => dispatch(navigateTo('/other-page?a=1'))}>Hi!</h1>
<h1 onClick={() => dispatch(navigateTo({ entity: 'other-page', query: { a: 2 }))}>Hola!</h1>
<input type="text" onInput={e => dispatch(setSomeText(e.target.value))} value={someText} />
<p>Change this, save file, and observe following text unchanged: {someText}</p>
</div>
}
function someTextReducer (state = '', action) {
switch (action.type) {
case 'SET_SOME_TEXT': return action.text
default: return state
}
}
function setSomeText (text) {
return { type: 'SET_SOME_TEXT', text }
}
// entities/other-page/index.jsx
module.exports = function ({ route }) {
return <h1>Param A is {route.query.a}</h1>
}
- client application consists of pages
- each page is a stateless functional component with dedicated reducer (although reducer is optional)
- application state has two parts: persistent (
app
) and changing (page
androute
as page ID) - whenever route changes, previous page state is lost, new page loaded and rendered, receiving complete application state (plus
dispatch
) as props
Function, starts client application, accepting following options.
Requied: yes
Function, must return webpack require.context
instance. Is used by router to require and render component according to route.
Required: no
Function, must be falsey, or equal to module.hot.accept
, provided by webpack. Simply put module.hot && module.hot.accept
here if you want to enable hot reload.
Requred: yes
String or { entity, action, id, query }
object, representing default route.
As example, route-string /books/edit/1?mode=extended
corresponds to route-object
{
entity: 'books',
action: 'edit',
id: '1',
query: { mode: 'extended' }
}
Required: no
String or { entity, action, id, query }
object, representing login route. Is used by default route reducer in case if any REST action returns 401 (not authenticated) or 403 (forbidden).
Required: no
Custom route reducer. In most cases you do not need to override default routing behaviour, but, if you need, it can be done by providing route reducer.
Required: no
Reducer for persistent part of application state (app
). As example, app
reducer can handle authentication-related actions, returing { user }
state.
Required: no
Array of middleware functions. As example, you could put here redux-thunk, also you could pass redux-logger middleware in development
environment.
Required: no
DOMNode
used for rendering of virtual node, returned by page component. By default, it's element with ID "root".
Object, represents dictionary of scope-related functions.
Function, creates scoped dispatch function. Scoped means that each action dispatched by it will contain scope
property.
Function, creates scoped reducer function. Scoped means that reducer will process actions of given scope, ignoring others.
Object, represents dictionary of actions.
Function, creates route change action. If silent
is true
, then URL change will be prevented.
Functon, creates route query change action. If replace
is true
, then query will be replaced by keyValues
, otherwise it will be extended by keyValues
. Note, you can pass { param: undefined }
to get rid of param
query parameter.
You must provide redux-thunk
middleware to use this action.
Function, creates read action. Whenever it's dispatched:
GET
request is started using givenurl
andqs
(query string object)${operationName}_STARTED
is fired withrequest
property (reference to request promise)- once request succeeded,
${operationName}_SUCCEEDED
is fired withresult
property (reference to response body) - in case of error,
{$operatioName}_FAILED
is dispatched witherror
property (in most cases it will be of typeRestRequestError
)
You must provide redux-thunk
middleware to use this action.
Function, creates write action. Whenever it's dispatched:
POST
request is started using givenurl
andbody
${operationName}_STARTED
is fired withrequest
property (reference to request promise)- once request succeeded,
${operationName}_SUCCEEDED
is fired withresult
property (reference to response body) - in case of error,
{$operatioName}_FAILED
is dispatched witherror
property (in most cases it will be of typeRestRequestError
)
You must provide redux-thunk
middleware to use this action.
Function, creates remove action. Whenever it's dispatched:
DELETE
request is started using givenurl
${operationName}_STARTED
is fired withrequest
property (reference to request promise)- once request succeeded,
${operationName}_SUCCEEDED
is fired - in case of error,
{$operatioName}_FAILED
is dispatched witherror
property (in most cases it will be of typeRestRequestError
)
You must provide redux-thunk
middleware to use this action.
Error, represents REST request error, consisting of statusCode
and body
properties, corresponding to ones from response.
- bored of complex bootstrap, requiring lots of prior knowledge
- bored of stateful OOP-based approaches, requiring obscure patching for hot reload or isomorphic rendering
- bored of components built on top of complex APIs, gathering in complex hierarchies
- excited about delivering more per unit of time using clear and straightforward tooling
- want to understand the stack from bottom to top
MIT