Session Controller is an entry point for your React application, it:
- provides application state storage
- re-renders app on state updates
- provides sensible way of splitting multi-page applications into asynchronously loaded modules
There are two classes in the module: Controller
and Session
:
Controller
passes business logic and application state data to page components. It helps to group common actions and UI components, and load them asynchronously from a separate chunk (if Webpack is used). One Controller per application page should be fine.Session
provides state storage and a method to change currently active controller.Session
also re-renders page when state is updated.
Session
constructor takes two arguments: mount-point for React and a list of controllers. For example:
import { Session } from 'session-controller';
import ControllerB from './ControllerB';
const controllers = {
ControllerA: () => import('./ControllerA'),
ControllerB: () => { return Promise.resolve({ default: ControllerB }) };
}
const session = new Session(document.getElementById('mount-point'), controllers);
Note that for controllers
object:
- each value is a function that returns a Promise, that resolves a module
- It will be either a
() => import('./ControllerA')
that creates a new async chunk. - or
() => { return Promise.resolve({ default: ControllerB }) }
, whereControllerB
was previously imported
- It will be either a
- resolved modules default export should be a class. that's why
ControllerB
value resolves{ default: ControllerB }
value. It also has to conform to providedController
interface - every key should be the same as resolved controller's value returned by
name
getter
Session constructor creates a store and subscribes to its updates to re-render current controller's view.
To change currently active application page, call session.mountController(controllerName: String)
- this function will try to import controller from controllers
object. E.g. session.mountController('ControllerA')
will try to import ControllerA
and render it's view
property on success.
Re-render attempts caused by state changes made during controller mount period will be ignored.
Session will try to mountController('ErrorController', { error })
in case of import failure. If no ErrorController
is found, an error will be thrown. ErrorController should not be loaded asynchronously.
All controllers are expected to:
- be constructed with two arguments
constructor(context: Object)
.context
is session's property, that providesstore
andmountController
references.payload
is extra data that can be used to construct initial application state necessary to render controller's view. - have
view
property, that is going to be React root component while controller is active - have
controllerWillMount(payload: Object)
method. Usuallypayload
is used to construct initial application state. This method is also called whenmountController
tries to set already active controller (note thatsession.controller.name
getter is used to check that). - have
dispose()
method. This method is called when another controller is going to be mounted.
A sample controller might look like this:
class ExampleController extends Controller {
constructor(context) {
super(context);
this.view = () => {
return <div>Hello world!</div>;
};
}
get name() {
return 'ExampleController'
}
controllerWillMount() {}
dispose() {}
}
This example doesn't show how to pass application state to controller's view.
To pass state to components you might want to use dependency injection. E.g. components-di module:
import { injectDeps } from 'components-di';
const actions = {
helloWorld: (context) => { console.log('hello world')! }
}
const view = () => {
return <div>Hello world!</div>;
};
class ExampleController extends Controller {
constructor(context) {
super(context);
this.view = injectDeps(context, actions)(view);
}
reset() {}
dispose() {}
}
In this case view's child components will be able to use provided context and actions via components-di useDeps
method.
It also might be a good idea to extend Session, instead of using it directly (edited excerpt from live project, that uses session-controller):
import { Session } from 'session-controller';
import request from 'superagent';
import routingService from 'services/RoutingService'; // listens to browser history changes
class SessionController extends Session {
constructor(mountPoint, controllers) {
super(mountPoint, controllers);
Object.assign(this.context, { config: { foo: 'bar' } });
routingService.add('home', '/', ({ query }) => {
request.get('/api/home').end((err, res) => {
if (!err && res.ok) {
this.mountController('HomeController', { data: res.body, query });
} else {
this.mountController('ErrorController', { error: err });
}
})
})
}
}
Controller:
constructor(context: Object)
controllerWillMount([payload: Object])
dispose()
get name()
view
- React component
Session:
constructor(mountPoint: Node, controllers: Object)
mountController(controllerName: String)