LeadScanr - finds leads from social media in real-time
Front-End, ML
Full stack
Ievgen Terpil
Vyacheslav Pytel
- Framework without improvements
- File structure
- Scalability
- Build tools
- framework with community
- easy to implement new features
- easy to support
- easy to test
- localization
- build tools
React Europe
(state, action) => state
{
type: 'DEPOSIT',
value: 10
}
function counter(state = 0, action) {
switch (action.type) {
case 'DEPOSIT':
return state + action.value
case 'WITHDRAW':
return state - action.value
default:
return state
}
}
Changes are made with pure functions
state tree
Single source of truth
const store =
createStore(reducer, initialState)
store.subscribe(render)
// -----------------------------------
store.dispatch(action)
State is read-only
const App = ({ state }) => {
<div>
Balance: {state}
</div>
}
........
Account = ({ balance, onDepositClick }) => {
<div>
<button onClick={onDepositClick}>
deposit
</button>
<div>Balance: {balance}</div>
</div>
}
connect(
mapStateToProps,
mapDispatchToProps
)(Account)
function mapStateToProps(state, ownProps) {
return { balance: state.balance }
}
function mapDispatchToProps(dispatch) {
return {
onDepositClick: () => dispatch(deposit())
}
}
@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 (Presentational)
Smart (Container)
logic
Redux's connect
binds cb for dumb
DOM markup and styles
reusable
your mini Bootstrap
{ type: 'FETCH_ACCOUNT_REQUEST' }
{ type: 'FETCH_ACCOUNT_SUCCESS', account: { ... } }
{ type: 'FETCH_ACCOUNT_FAILURE', error: 'Oops' }
actions
action creators
function receiveAccount(account) {
return {
type: FETCH_ACCOUNT_SUCCESS,
account
}
}
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
import { CALL_API } from `redux-api-middleware`;
{
[CALL_API]: {
endpoint: 'api/account',
method: 'GET',
types: ['REQUEST', 'SUCCESS', 'FAILURE']
}
}
action creators
declarative
view
actions
reducers
i
i
i
feature1
feature2
feature3
view
actions
reducers
i
i
i
feature1
feature2
feature3
set of standart components
view
actions
reducers
i
i
i
feature1
feature2
feature3
set of standart components
main reducer
view
actions
reducers
i
i
i
feature1
feature2
feature3
set of standart components
main reducer
all actions
our solutions
import reducers from './features/**/reducers.js';
yo redux-component
import actions from './features/**/actions.js';
account/
Account.jsx
actions.js
reducres.js
button/
Button.jsx
b-button.scss
Smart (feature)
Dump
- 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
Action Creator
Service 1
Service 2
Service 3
A
A
A
Action Creator
Service 1
Service 2
Service 3
A
A
A
Action Creator
Action Creator
applyMiddlewares(middleware1, middleware2, ...)
dispatch([
startProcessCard(),
setCreditCard(card),
getOffers(),
buy(plan.get('id')),
pushState(null, '/account', {})
]);
async waterfall
orchestrating complex/asynchronous operations
Saga
Service 1
Service 2
Service 3
Generator functions (ES6) as action creators
1
function* fetchAccount() {
const account = yield Api.fetch('/account')
console.log(account)
}
function* watchFetchAccount() {
yield* takeEvery('ACCOUNT_REQUESTED', fetchAccount)
}
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
Dispatching actions
import { call } from 'redux-saga/effects'
function* fetchAccount(dispatch) {
const account = yield call(Api.fetch, '/account')
dispatch({ type: 'ACCOUNT_RECEIVED', account })
}
import { call, put } from 'redux-saga/effects'
function* fetchAccount() {
const account = yield call(Api.fetch, '/account')
yield put({ type: 'ACCOUNT_RECEIVED', products })
}
Testing
const iterator = fetchAccount()
assert.deepEqual(
iterator.next().value,
call(Api.fetch, '/account')
)
// create a fake response
const account = { balance: 10 }
// expects a dispatch instruction
assert.deepEqual(
iterator.next(account).value,
put({ type: 'ACCOUNT_RECEIVED', account })
)}
takeEvery
takeLatest
Saga
take
put
call
A
Api
Dispatcher
fork
Saga
cancel
flux
redux
redux:
(state, action) => state
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
Ievgen Terpil
Vyacheslav Pytel