mail: vnovick@gmail.com
twitter: @VladimirNovick
github: vnovick
facebook: vnovickdev
Redux
Middleware is the suggested way to extend Redux with custom functionality. Middleware lets you wrap the store’s dispatch method for fun and profit
import createLogger from 'redux-logger'
import { createStore } from 'redux'
let store = createStore(rootReducer,
applyMiddleware(thunk, createLogger())
)
const applyStateTransform = (action) => {
let { transform, state } = action;
return transform ? transform(state) || state : state
}
const fetcher = store => next => action => {
if (action.type === '@@fetcher/FETCH'){
let { url, method, resolve, reject, error } = action.state;
fetch(url, {
method
}).then((result)=> {
let resolveAction = resolve(result);
let errorAction = error(result);
return result.ok ?
next({
type: `@@fetcher/${resolveAction.type}`,
state: applyStateTransform(resolveAction)
}) :
next({
type: `@@fetcher/${errorAction.type}`,
state: applyStateTransform(errorAction)
})
}).catch((err) => {
let rejectAction = reject(err);
return next({
type: `@@fetcher/${rejectAction.type}`,
state: applyStateTransform(rejectAction)
})
});
}
return next(action)
}
////Fetch Action Creator
export const fetchFromServer = (url, method) => dispatch => {
dispatch({
type: '@@fetcher/FETCH',
state: {
url,
method,
resolve: result => {
return {
type: 'FETCH_SUCCESS',
state: result
}
},
reject: err => {
return {
type: 'FETCH_ERROR',
state: err
}
},
error: err => {
return {
type: 'RESPONSE_ERROR',
state: err,
transform: ()=>{
dispatch({
type: 'Lalala'
})
}
}
}
}
})
}
// Usage
componentDidMount(){
let { dispatch } = this.props;
dispatch(inject('player', this))
console.log(appStore.getState(this))
}
//Result will be player stateKey value
//Inject Action Creator
export const inject = (state, component) => {
return {
type: '@@redux/INJECT',
state
component
}
}
function diStoreEnhancer(){
return createStore => (...args) => {
const store = createStore(...args);
const diContainer = []
const dispatch = function decoratedDispatch(action){
if (action.type === '@@redux/INJECT') {
const { component, state } = action;
diContainer.push({ component, stateKey: state })
}
return store.dispatch(action)
}
const getState = function decoratedState() {
if (component) {
return store.getState()[diContainer
.filter(di => di.component === component)
.reduce( (prev,next) => next, {})
.stateKey
];
}
return store.getState()
}
return {
...store,
...{
dispatch,
getState
}
};
};
}
import { compose } from 'redux'
const createComposedStore = compose(
storeEnhancer(),
applyMiddleware(fetcher, createLogger()
)(createStore);
export default createComposedStore(rootReducer);
Container(Smart) Component:
Specifies data and behavior for component but does not know anything about appearance
Presentational(Dumb) Component:
Specify appearance of component but knows nothing about behavior
The problem:
If you have a component tree, store must be passed through props for all components in the tree.
The solution:
Only container components get store from context when defining contextTypes.
import { Provider } from 'react-redux'
import { appStore} from 'appStore'
<Provider store={ appStore }>
<PlayerContainer/>
</Provider>
import * from 'actionCreators';
import { connect } from 'react-redux';
// ...
// ...Player React Component definition
function mapStateToProps(state){
const { src, poster, showControls } = state.player;
return {
src, poster, showControls
}
}
export const PlayerContainer = connect(
mapStateToProps
)(Player);
import React from 'react';
import * from 'actionCreators';
import { connect } from 'react-redux';
class Player extends React.Component {
play(){
this.props.dispatch(actionCreators.play())
}
render() {
return (
<PlayerWrapper>
<PlayerSurface/>
<PlayerControls play={this.play.bind(this}/>
</PlayerWrapper>
)
}
}
//export PlayerContainer with connect as in previous slide
import React from 'react';
import * from 'actionCreators';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class Player extends React.Component {
render() {
return (
<PlayerWrapper>
<PlayerSurface/>
<PlayerControls play={this.props.actions.play} />
</PlayerWrapper>
)
}
}
function mapStateToProps(state){
//...same as previous slide
}
function mapDispatchToProps(dispatch){
return {
actions: bindActionCreators(actionCreators, dispatch)
}
}
export const PlayerContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Player);
Provider: wraps root component and makes possible to use connect()
connect: executed on every state change if specified. returned object is merged into component props
react@90min.com