You can find animated version here
ABOUT USABOUT US
- finds leads from social media in
real-time
LeadScanr
Front-End, MLFull stack
Ievgen TerpilVyacheslav Pytel
@JenyaTerpil@vyacheslav_de
CHAPTER 0CHAPTER 0
INTROINTRO
INITIAL POINTINITIAL POINT
- Framework without improvements
- File structure
- Scalability
- Build tools
IDEAL APPIDEAL APP
- framework with community
- easy to implement new features
- easy to support
- easy to test
- localization
- build tools
CHAPTER 1CHAPTER 1
HYPEHYPE
FLUXFLUX
REDUXREDUX
@dan_abramov React Europe
REDUXREDUX
(state, action) => state
REDUXREDUX
REDUXREDUX
{
type: 'DEPOSIT',
value: 10
}
ACTIONACTION
REDUXREDUX
function counter(state = 0, action) {
switch (action.type) {
case 'DEPOSIT':
return state + action.value
case 'WITHDRAW':
return state - action.value
default:
return state
}
}
REDUCERREDUCER
Changes are made with pure functions
REDUXREDUX
STATESTATE
state tree
Single source of truth
REDUXREDUX
const store =
createStore(reducer, initialState)
store.subscribe(render)
// -----------------------------------
store.dispatch(action)
STORESTORE
State is read-only
REDUXREDUX
const App = ({ state }) => {
<div>
Balance: {state}
</div>
}
VIEWVIEW
VIEW LAYERVIEW LAYER
redux
react-redux ng-redux ........
REACT AS VIEW LAYERREACT AS VIEW LAYER
REACT AS VIEW LAYERREACT AS VIEW LAYER
Account = ({ balance, onDepositClick }) => {
<div>
<button onClick={onDepositClick}>
deposit
</button>
<div>Balance: {balance}</div>
</div>
}
REACT AS VIEW LAYERREACT AS VIEW LAYER
react-redux
connect(
mapStateToProps,
mapDispatchToProps
)(Account)
function mapStateToProps(state, ownProps) {
return { balance: state.balance }
}
function mapDispatchToProps(dispatch) {
return {
onDepositClick: () => dispatch(deposit())
}
}
REACT AS VIEW LAYERREACT AS VIEW LAYER
@connect(({ Subscriptions, Profile }) => ({
currentPlan: Subscriptions.get('currentPlan'),
userName: Profile.get('userName')
}))
export default class Subscriptions extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
userName: PropTypes.string,
currentPlan: PropTypes.object
}
...
}
our case with ES7
decorator
DUMB AND SMARTDUMB AND SMART
Dumb (Presentational)
Presentational and Container Components
Smart (Container)
real view
uses only props
DOM markup and styles
functional components
logic
Redux's connect
binds cb for dumb
DOM markup and styles
reusable
your mini Bootstrap
SIDE EFFECTSSIDE EFFECTS
SIDE EFFECTSSIDE EFFECTS
SIDE EFFECTS - BASE APPROACHSIDE EFFECTS - BASE APPROACH
{ type: 'FETCH_ACCOUNT_REQUEST' }
{ type: 'FETCH_ACCOUNT_SUCCESS', account: { ... } }
{ type: 'FETCH_ACCOUNT_FAILURE', error: 'Oops' }
function receiveAccount(account) {
return {
type: FETCH_ACCOUNT_SUCCESS,
account
}
}
actions
action creators
SIDE EFFECTS - BASE APPROACHSIDE EFFECTS - BASE APPROACH
redux-thunk
let getAccount = id => dispatch => {
dispatch(requestAccount(id));
return fetch('/account', id)
.then(account => dispatch(receiveAccount(account)))
.catch(error => dispatch(throwError(error)));
};
complex action creator
API request
redux-api-middleware
import { CALL_API } from `redux-api-middleware`;
{
[CALL_API]: {
endpoint: 'api/account',
method: 'GET',
types: ['REQUEST', 'SUCCESS', 'FAILURE']
}
}
action creators
SIDE EFFECTS -SIDE EFFECTS - LESS BOILERPLATELESS BOILERPLATE
declarative
SIDE EFFECTS -SIDE EFFECTS - LESS BOILERPLATELESS BOILERPLATE
CHAPTER 2CHAPTER 2
PRODUCTIONPRODUCTION
FEATURE FOLDERSFEATURE FOLDERS
FEATURE FOLDERSFEATURE FOLDERS
view actions reducers
i
i
i
feature1
feature2
feature3
FEATURE FOLDERSFEATURE FOLDERS
view actions reducers
i
i
i
feature1
feature2
feature3
set of standart components
FEATURE FOLDERSFEATURE FOLDERS
view actions reducers
i
i
i
feature1
feature2
feature3
set of standart components
main reducer
FEATURE FOLDERSFEATURE FOLDERS
view actions reducers
i
i
i
feature1
feature2
feature3
set of standart components
main reducer
all actions
FEATURE FOLDERSFEATURE FOLDERS
our solutions
import-glob
import reducers from './features/**/reducers.js';
generator-redux-component
yo redux-component
import actions from './features/**/actions.js';
account/
Account.js
x
actions.js
reducres.js
button/
Button.jsx
b-button.scss
Smart (feature) Dump
redux-promise - if it receives a promise, it will dispatch the resolved
value of the promise
export function getAccount() {
return async (api) => {
return {
type: events.ACCOUNT_READY,
account: await api.options.account.get()
};
};
}
our case with ES7
SIDE EFFECTS -SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE
Action Creator
Service 1
Service 2
Service 3
A
A
A
SIDE EFFECTS -SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE
Action Creator
Service 1
Service 2
Service 3
A
A
A
SIDE EFFECTS -SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE
Action Creator
Action Creator
SIDE EFFECTS -SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE
applyMiddlewares(middleware1, middleware2, ...)
redux-chain-middleware
dispatch([
startProcessCard(),
setCreditCard(card),
getOffers(),
buy(plan.get('id')),
pushState(null, '/account', {})
]);
async waterfall
SIDE EFFECTS -SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE
CHAPTER 3CHAPTER 3
SAGASAGA
SAGASAGA
orchestrating complex/asynchronous operations
Saga
Service 1
Service 2
Service 3
REDUX-SAGAREDUX-SAGA
Generator functions (ES6) as action creators1
function* fetchAccount() {
const account = yield Api.fetch('/account')
console.log(account)
}
function* watchFetchAccount() {
yield* takeEvery('ACCOUNT_REQUESTED', fetchAccount)
}
REDUX-SAGAREDUX-SAGA
Declarative Effects
{
CALL: {
fn: Api.fetch,
args: ['./account']
}
}
2
yield only a description of
the function invocation
import { call } from 'redux-saga/effects'
function* fetchAccount() {
const account = yield call(Api.fetch, '/account')
// ...
}
- making our code testable
import { call, put } from 'redux-saga/effects'
function* fetchAccount() {
const account = yield call(Api.fetch, '/account')
yield put({ type: 'ACCOUNT_RECEIVED', products })
}
REDUX-SAGAREDUX-SAGA
Dispatching actions
import { call } from 'redux-saga/effects'
function* fetchAccount(dispatch) {
const account = yield call(Api.fetch, '/account')
dispatch({ type: 'ACCOUNT_RECEIVED', account })
}
assert.deepEqual(
iterator.next().value,
call(Api.fetch, '/account')
)
REDUX-SAGAREDUX-SAGA
Testing
const iterator = fetchAccount()
// create a fake response
const account = { balance: 10 }
// expects a dispatch instruction
assert.deepEqual(
iterator.next(account).value,
put({ type: 'ACCOUNT_RECEIVED', account })
)}
REDUX-SAGA APIREDUX-SAGA API
takeEvery
takeLatest
Saga
take put
call
A
Api
Dispatcher
fork
Saga
cancel
CONCLUSIONCONCLUSION
flux redux
redux: (state, action) => state
CONCLUSIONCONCLUSION
flux redux
redux: (state, action) => state
use feature folders
create collection of Dumb components
side-effects:
easy complex
redux-thunk
redux-promise
redux-saga
i
THANK YOU FOR YOURTHANK YOU FOR YOUR
ATTENTIONATTENTION
Ievgen TerpilVyacheslav Pytel
@JenyaTerpil@vyacheslav_de
Slawaq terpiljenya
LeadScanr
You can find animated version here

Redux. From twitter hype to production