Domain Model Guide

The Reactium domain model is the sauce on the ribs...


FileDescription
index.jsMain component file
plugin.jsPlugin configuration file
route.jsRoute configuration file
services.jsUtility functions and AJAX requests
 
Redux FileDescription
[Domain].jsRedux component file that gets wrapped by react-redux.connect()
actionTypes.jsRedux actionTypes
actions.jsRedux actions
reducers.jsRedux reducers
state.jsRedux default state

The index.js File

The main entry point to the domain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// index.js for Test Domain

import React, { Component } from 'react';

export default class Test extends Component {
    constructor(props) {
        super(props);

        this.state = {
            count: 0,
        };
    }

    click() {
        let { count } = this.state;
        count += 1;
        this.setState({ count });
    }

    render() {
        const { count } = this.state;

        return (
            <div>
                <button type='button' onClick={() => this.click()}>
                    Click Me
                </button>
                <div>{count}</div>
            </div>
        );
    }
}
Redux Specification

When creating a domain with Redux, the index.js becomes a container and wraps the [Domain].js file with the react-redux connect() function. You will need to map state to properties and map dispatchers to actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Redux index.js wrapper for Test domain

import Test from './Test';
import deps from 'dependencies';
import { connect } from 'react-redux';

// Map state to properties
const mapStateToProps = (state, props) => Object.assign({}, state.Test, props);

// Map dispatchers to actions
const mapDispatchToProps = (dispatch, props) => ({
    click: () => dispatch(deps().actions.Test.click()),
});

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Test);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Test.js passed to connect() in index.js

import React, { Component } from 'react';

export default class Test extends Component {

    render() {

        // Uses the click() function mapped from mapDispatchToProps()
        const { click, count } = this.props;

        return (
            <div>
                <button type='button' onClick={() => click()}>
                    Click Me
                </button>
                <div>{count}</div>
            </div>
        );
    }
}

The plugin.js File

The plugin configuration file is used to declare a domain as a plugin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// plugin.js file

import HomeNavigation from './index';

export default {
    /**
     * Required - used as rendering key. Make this unique.
     * @type {String}
     */
    id: 'home-page-main-navigation',

    /**
     * By default plugins in zone are rendering in ascending order.
     * @type {Number}
     */
    order: 0,

    /**
     * One or more zones this component should render.
     * @type {String|Array}
     */
    zone: 'navigation',

    /**
     * Component to render. May also be a string, and
     * the component will be looked up in components directory.
     * @type {Component|String}
     */
    component: HomeNavigation,

    /**
     * (Optional) additional search subpaths to use to find the component,
     * if String provided for component property.
     * @type {[type]}
     *
     * e.g. If component is a string 'TextInput', uncommenting the line below would
     * look in components/common-ui/form/inputs and components/general to find the component 'TextInput'
     */
    // paths: ['common-ui/form/inputs', 'general']

    /**
     * Additional params: (optional)
     *
     * Any additional properties you provide below, will be provided as params to the component when rendered.
     *
     * e.g. Below will be provided to the HomeNavigation, <HomeNavigation pageType="home" />
     * These can also be used to help sort or filter plugins.
     * @type {Mixed}
     */
    pageType: 'home',
};

See the Plugin Guide for more information.


The route.js File

Reactium aggregates all route.js files into a list of routes used to render React Router <Route /> components and observed by the Reactium <RouteObserver /> component.

A route can have any property that is supported by <Route /> (e.g. path, exact, strict, location, component, render, and children).

A typical route.js file may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// route.js file

// Import your component
import MyComponent from './index';

// Import the aggregated actions (optional)
import deps from 'dependencies';

export default {
    // Make this higher number to have route evaluated later (default 0)
    order: 0,

    // Route patterns: more specific routes should be entered first
    path: [
        '/my-component/:paramA/:paramB',
        '/my-component/:paramA',
        '/my-component'
    ],

    // Should the Route be exact?
    exact: true,

    // the component to load for this route
    component: MyComponent,

    // (optional) a Redux thunk action to load data for this component
    load: params => deps().actions.MyComponent.mount(params),
}

The services.js File

Reactium aggregates all services.js files into the services property of the dependencies module default export.

A typical services.js file may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// services.js file

import axios from 'axios';
import { restHeaders } from 'dependencies';

const restAPI = 'http://demo3914762.mockable.io';

const fetchHello = () => {
    const hdr = restHeaders();
    return axios.get(restAPI + '/hello', { headers: hdr }).then(({ data }) => data);
};

const fetchGoodBye = () => {
    const hdr = restHeaders();
    return axios.get(restAPI + 'goodbye', { headers: hdr }).then(({ data }) => data);
};

export default {
    fetchHello,
    fetchGoodBye,
};
Usage:

In your actions.js file you would do something like:

1
2
3
4
5
6
7
8
9
10
11
// actions.js file

import deps from 'dependencies';

export default {
    mount: params => dispatch => {
        deps().services.Test.fetchHello().then(data => {
            dispatch({ type: deps().actionTypes.TEST_MOUNT, payload: data });
        });
    },
};

The actionTypes.js File

Reactium aggregates all actionTypes.js files into the actionTypes property of the dependencies.

A typical actionTypes.js file may look like this:

1
2
3
4
export default {
    TEST_MOUNT: 'TEST_MOUNT',
    TEST_CLICK: 'TEST_CLICK',
};

Note: Unlike other domain dependencies, actionTypes are flattened together in deps().actionTypes with no domain.
So be sure to uniquely name your actionTypes.

To access the actionTypes, import them into your component:

1
import deps from 'dependencies';

Usage in an action.js file:

1
dispatch({ type: deps().actionTypes.TEST_MOUNT, data: data });

Usage in a reducers.js file:

1
2
3
4
5
6
...
case deps().actionTypes.TEST_MOUNT: {
    const newState = { ...state, mounted: true };
    return newState;
}
...

The actions.js File

Reactium aggregates all actions.js files into the actions property of the dependencies.

A typical actions.js file may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
// actions.js file
import deps from 'dependencies';

export default {
    mount: params => dispatch => {
        dispatch({ type: deps().actionTypes.TEST_MOUNT, data: params });
    },

    click: () => dispatch => {
        dispatch({ type: deps().actionTypes.TEST_CLICK });
    },
};

To access the actions simply import the dependencies:

1
import deps from 'dependencies';

Then use an action by targeting the component domain that created the action:

1
deps().actions.Test.mount({ some: 'params' });

The reducers.js File

Reactium aggregates all reducers.js files into the reducers property of the dependencies.

A typical reducers.js file may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// reducer.js file

import deps from 'dependencies';

export default (state = {}, action) => {
    let newState;

    switch (action.type) {
        case deps().actionTypes.TEST_MOUNT: {
            newState = { ...state, ...action.payload };
            return newState;
        }

        case deps().actionTypes.TEST_CLICK: {
            let { count = 0 } = state;

            count += 1;

            newState = { ...state, count };
            return newState;
        }

        default: {
            return state;
        }
    }
};

The state.js File

Reactium aggregates all state.js files into the Redux store for the application.

A typical state.js file may look like this:

1
2
3
4
export default {
    some: 'value',
    another: 1,
};

To persist the domain state to local storage for insertion as initial state on hard reload, add a persist property:

1
2
3
4
5
6
7
8
export default {
    some: 'value',
    another: 1,

    // See https://www.npmjs.com/package/redux-local-persist for additional
    // configuration options.
    persist: true,
};