@Deividas Karžinauskas answer makes perfect sense when you have a couple of nested components and I think that is the reasonable way to go in such scenario, but since I wanted my component to be totally independent off any other component, yet testable and not containing any data logic I just came up with slightly different approach. Of course Deividas Karžinauskas answer helped.
To make my component totally independent I just made it yet another container, all the side effects are handled by action/action creators and it still is testable while data is calculated by it's own independent reducer. I then used redux to pass the state as props.
Code Sample
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
class RolesField extends React.Component {
componentWillMount() {
//if not fetched then fetch
this.props.fetchRoles();
}
roles() {
let {roles} = this.props;
if (roles.length > 0) {
const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;
let rolesUi = roles.map((role, index) => {
return (
<div className="checkbox" key={index}>
<label htmlFor={input.name}>
<input {...input} placeholder={placeholder} type={type}/>
{role.name} <a href="#">( Show Permissions )</a>
</label>
</div>
);
});
return rolesUi;
}
}
render() {
const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;
return (
<div className="form-group">
<label className="col-sm-2 control-label" htmlFor={input.name}>
Administrative Roles
</label>
<div className="col-sm-10">
{/*This section should be loaded from server,
the component should dispatch the event and role action should load the roles.*/}
{this.roles()}
</div>
</div>
);
}
}
//map state to props.
function mapStateToProps(state) {
return {
roles: state.roles.roles
}
}
function mapDispatchToProps(dispatch) {
let actionCreators = {
fetchRoles
};
return bindActionCreators(actionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(RolesField);
/*Action Types*/
const ROLE_FETCH_START = 'ROLE_FETCH_START';
const ROLE_FETCH_COMPLETE = 'ROLE_FETCH_COMPLETE';
/*End Action Types*/
/**
* Action Creator
* @returns {{type: string, payload: [*,*,*,*]}}
*/
function fetchRoles() {
const roles = [
{'id': 1, 'name': 'Super Admin'},
{'id': 2, 'name': 'Admin'},
{'id': 3, 'name': 'Manager'},
{'id': 4, 'name': 'Technical Lead'}
];
/*Action*/
return {
type: ROLE_FETCH_COMPLETE,
payload: roles
};
}
/**
* Roles Reducer
* @param state
* @param action
* @returns {*}
*/
export function rolesReducer(state = {roles: [], fetched: false}, action) {
switch (action.type) {
case ROLE_FETCH_COMPLETE :
return {
...state,
fetched: true,
roles: action.payload
};
break;
}
return state;
}
And to re-use the component in redux-form I did
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import {renderInputField, renderDropdownList} from '../../page/components/field.jsx';
import RolesField from '../../page/containers/fields/roles.jsx';
import Box from '../../page/components/box.jsx';
import ActionButtons from '../../page/components/action-buttons';
class UserForm extends React.Component {
actionButtons() {
return [
{
'type' : 'submit',
'label' : 'Save',
'className' : 'btn-primary'
},
{
'type' : 'button',
'label' : 'Cancel',
'className' : 'btn-success',
'onClick' : this.props.reset
},
{
'type' : 'button',
'label' : 'Delete',
'className' : 'btn-danger'
}
]
}
render() {
const { handleSubmit } = this.props;
return (
<form className="form-horizontal" role="form" onSubmit={handleSubmit}>
<Box title="Add New User">
<ActionButtons buttons = {this.actionButtons()} />
<Field
name="roles[]"
component={RolesField}
label="Technical Lead"
type="checkbox"
className="form-control" />
</Box>
</form>
);
}
}
UserForm = reduxForm({
form: 'userForm',
validate
})(UserForm);
export default UserForm;
Here is the revised diagram. I am not sure if this is a best practice or there are any drawbacks associated with it, but it just worked in my use case.
State chart:

Apologies for the diagram not being so clear.
