React modern ecosystem survey 2017
* React components
* React flow
* HOCs, recompose library
* Redux flow and parts - constants, action creators, reducers, middleware, store/state, selectors (reselect)
* Epics - rxjs middleware
* Structuring your project - redux ducks
Few words about React
It's just a view layer for your applications. React allows you to sync up your data with DOM. Most tools have very simple API - learn JS programming, not tools.
React components
Stateless component - pure function. Render data from props. Use it for representation purpose.
import React from 'react'
const AwesomeLabel = props => <label className="my-label-style">{props.labelText}</label>
Stateful component - class, extends React.Component. Has its own state. Updates state itself.
import React, { PureComponent } from 'react'
class AwesomeInput extends PureComponent {
state = { inputValue: '' }
render() {
return(
<input
type="text"
onChange={event => this.setState({ inputValue: event.target.value })}
value={this.state.inputValue}
/>)
}
}
Container component - wrapper component that passes props/state to multiple stateless components.
import React from 'react'
const AwesomeButton = ({ children, className, onClick, ...restProps }) => <button {...{ className, onClick }} ...restProps>{children}</button>
const AwesomeIconButton = props =>
<AwesomeButton className="btn" onClick={props.onClick} onHover={props.onHover}>
<span>
<i classname="fa"/>
</span>
{props.caption}
</AwesomeButton>
React flow
Mostly one-way data flow. Data passing by props from component to component. Component could have internal state and invokes render() function when state/props changed.
When we call setState or have intra-component communication, the diagram is not so pretty. The data flow loops back on itself, each prop no longer has a single origin. The application is fundamentally more complicated — whorls of confusion instead of a simple cycle. Components can re-render needlessly, and property thrashing is possible.
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
class AwesomeInput {
static propTypes = {
placeholder: PropTypes.string.isRequired,
setParentComponentValueCallback: PropTypes.func.isRequired,
isMountedCallback: PropTypes.func.isRequired,
}
state = { inputValue: '' }
handleChange = event => {
this.setState({ inputValue: event.target.value },
() => this.props.setParentComponentValueCallback(event.target.value)
);
}
render() {
this.props.isMountedCallback();
return(
<input
type="text"
placeholder={this.props.placeholder}
value={this.state.inputValue}
onChange={this.handleChange}
/>)
}
}
HOCs, recompose library
Higher Order Components is a great Pattern - allow you to reuse components, state abstraction and manipulation, simplify testing. A Higher Order Component is just a React Component that wraps another one. Recompose is a React utility belt for function components and higher-order components. Think of it like lodash for React.
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { lifecycle } from 'recompose';
import PTPanel from '../../presentational/PTPanel/PTPanel';
import PatientsChart from '../../containers/PatientsChart/PatientsChart';
import patientsSelector from './selectors';
import { fetchPatientsRequest } from '../../../ducks/feth-patients.duck';
import { patientsDepartments, patientsAges } from '../../../config/patients.constants';
const fetchPatientsOnMount = ({
componentDidMount() {
this.props.actions.fetchPatientsRequest()
},
});
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators({ fetchPatientsRequest }, dispatch) });
@connect(patientsSelector, mapDispatchToProps)
@lifecycle(fetchPatientsOnMount)
export default class SystemDashboard extends PureComponent {
static propTypes = {
patientsByAge: PropTypes.arrayOf(PropTypes.array).isRequired,
patientsByDepartment: PropTypes.arrayOf(PropTypes.array).isRequired,
};
render() {
const { patientsByAge } = this.props;
return (
<section className="page-wrapper">
<PTPanel header={<h3 className="panel-title"><i className="fa fa-bar-chart" /> Patients By Age</h3>}>
<PatientsChart
title="Patients By Age"
patients={patientsByAge}
/>
</PTPanel>
</section>)
}
}
Redux flow and parts
Redux is a predictable state container.
* Redux creates a store/state.
* Redux then creates reducers (listeners) that listen for actions (events)
* React components can dispatch (publish) these events via store.dispatch
* Redux then reduced (recalculated) state based off the dispatched action and returns a new copy of state.
* Once state has been reduced, React is free to re-render based off the new state.
import { Observable } from 'rxjs';
import { ajax } from 'rxjs/observable/dom/ajax';
import { createAction } from 'redux-actions';
import { usersUrls } from '../config/server-urls.constants'
export const FETCH_USER_ACCOUNT_REQUEST = 'FETCH_USER_ACCOUNT_REQUEST';
export const FETCH_USER_ACCOUNT_SUCCESS = 'FETCH_USER_ACCOUNT_SUCCESS';
export const FETCH_USER_ACCOUNT_FAILURE = 'FETCH_USER_ACCOUNT_FAILURE';
export const fetchUserAccountRequest = createAction(FETCH_USER_ACCOUNT_REQUEST);
export const fetchUserAccountSuccess = createAction(FETCH_USER_ACCOUNT_SUCCESS);
export const fetchUserAccountFailure = createAction(FETCH_USER_ACCOUNT_FAILURE);
export const fetchUserAccountEpic = (action$, store) =>
action$.ofType(FETCH_USER_ACCOUNT_REQUEST)
.mergeMap(() =>
ajax.getJSON(usersUrls.USER_ACCOUNT_URL, {
headers: { Cookie: store.getState().credentials.cookie },
})
.map(fetchUserAccountSuccess)
.catch(error => Observable.of(fetchUserAccountFailure(error)))
);
export default function reducer(userAccount = {}, action) {
switch (action.type) {
case FETCH_USER_ACCOUNT_SUCCESS:
return action.payload;
default:
return userAccount;
}
}
Epics - rxjs middleware
Epic is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.
function (action$: Observable<Action>, store: Store): Observable<Action>;
Structuring your project - redux ducks
It's a teсhnique to organize your constants, action creators, middleware, reducers in one module. A module...
MUST export default a function called reducer() MUST export its action creators as functions MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library