mail: vnovick@gmail.com
twitter: @VladimirNovick
github: vnovick
facebook: vnovickdev
Redux
import { createStore, combineReducers, } from 'redux';
import app from './appReducer';
import user from './userReducer';
const rootReducer = combineReducers({
app,
user
});
const store = createStore(rootReducer)
import { FANVOICE_ACTION_TYPES } from './fanvoiceActions.js';
const INITIAL_STATE = {
IS_INITIALIZED: false
};
function setState(state, newState){
return { ...state, ...newState };
}
export function fanvoiceReducer(state = INITIAL_STATE, action){
if (FANVOICE_ACTION_TYPES) {
switch (action.type) {
case FANVOICE_ACTION_TYPES.FETCH.SUCCESS:
case FANVOICE_ACTION_TYPES.FETCH.FAILED:
return setState(state, action.state);
default:
return state;
}
}
return state
}
function displayPopup(shouldShowPopup){
return {
type: TOGGLE_POPUP,
state: {
shouldShowPopup
}
};
}
export const getVideos = (api, userAuth) => (dispatch, getState) =>{
const state = getState();
const { apiKey, apiUrl } = state.app.config.api;
const { accessToken, id } = state.user.auth;
const url = `${apiUrl}/filter/videos/sort/user_id=${id}`,
dispatch({
type: '@@fetcher/FETCH',
state: {
url,
method,
resolve: result => ({
type: 'FETCH_SUCCESS',
state: result
}),
reject: err => ({
type: 'FETCH_ERROR',
state: err
}),
error: err => ({
type: 'RESPONSE_ERROR',
state: err,
transform: () => dispatch({ type: 'CUSTOM_ERROR_ACTION' })
})
}
});
};
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
const TodoApp = ({ store }) => (
<div>
<AddTodo store={store} />
<VisibleTodoList store={store} />
<Footer store={store} />
</div>
);
function getVisibleTodos(){
//...
}
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.props;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { getState, dispatch } = this.props.store;
const { todos, visibilityFilter } = store.getState();
return (
<TodoList
todos={ getVisibleTodos( todos, visibilityFilter ) }
onTodoClick={id => dispatch({ type: 'TOGGLE_TODO', id }) }
/>
);
}
}
The problem:
If you have a component tree, store must be passed through props for all components in the tree. It requires additional glue of subscribing, unsubscribing, getting state e.t.c
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);
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);
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