1. Redux
yet another flux implementation ?
Nils Petersohn | twitter.com/snackycracky | alan@turing.technology
2. agenda
• main ideas and overview of redux components
• actions as pure functions
• immutability and mutations in redux
• sync flow
• functional composition and redux middleware
• async actions
• reactjs bindings
• experiences refactoring to redux
3. what does it do ?
• architecture to manage state
• Redux attempts to make state mutations predictable by putting
restrictions on how and when updates can happen.
• does not attempt to solve the problems that Relay wants to
solve
• Relies heavily on functional programming paradigms
4. ideas and components
• Single source of truth
• The state is stored in an object tree inside a single store.
• State is read-only
• The only way to mutate the state is to emit an action, an
object describing what happened.
• Mutations are written as pure functions
• logic for changes triggered by those actions is inside
“pure reducers”.
5. pure functions
• are encapsulated constructs with deterministic
behaviour
• trivial but predictable aka. easy testable
• end up with hundreds of ever repeating arguments?
• no, higher order functions and function compositions
to the rescue
6. redux-actions as pure
functions
• important difference to Flux: no dispatching inside
the action
• there is no Dispatcher at all; pure functions do not
need to be managed, they need to be composed.
//Action
function addGoing(memberId){
return { action: "ADD_GOING", memberId: memberId };
}
7. immutability
• once “something” is created, it can’t be changed anymore
• how to mutate then ?
• make a new “something” with the desired changes
• benefits ?
• 100% sure that this “something” is not changed while being
passed around in the flow.
• don't have to worry about the underlying data being changed by
another entity
• (concurrency)
8. reducers
• invoked with current state and the desired actions as
arguments to change the state create a new state
• one store also means one reducer ?
• no, there can be multiple reducers for the central
store. Also reducers calling other reducers is
common to modify deeply nested states.
• redux will bind them together with
`combineReducers(reducerList)` where each
of them manages it’s own part of the global state
11. a smart react container
• connects to everything redux.
• actions and state are injected in the props
• avoid rendering HTML, let the dumb components do that
import {addGoing, addNotGoing} from "reducers/meetup"
import {hideAlerts} from "reducers/users"
@connect(
state => ({
going: state.visibleMeetup.going,
notGoing: state.visibleMeetup.notGoing,
comments: state.visibleMeetup.comments,
memberId: state.user.memberId,
userAlerts: state.user.alerts
}),
dispatch => bindActionCreators({
addGoing,
addNotGoing,
hideAlerts}, dispatch))
export default class MeetupPage extends Component {
render() {
const {going,notGoing,addGoing,addNotGoing,userAlerts,hideAlerts,memberId} = this.props;
<div>
<UserInfo userNotifications={userAlerts} hideAlerts={hideAlerts}/>
<AllOtherPageContent allOtherActions={...} />
<GoingNotGoingComponent addGoing={addGoing} going={going} ... />
</div>
}
}
12. a dumb react component
export default class GoingNotGoingComponent extends Component {
render() {
const {going, notGoing, memberId, addGoing, addNotGoing}= this.props;
<div>
<button onClick={addGoing.bind(null, memberId)}>I am Going</button>
<ul className="going">
{going.map(member => return <li>{member}</li>)}
</ul>
<hr/>
<button onClick={addNotGoing.bind(null, memberId)}>I am NOT Going</button>
<ul className="notGoing">
{going.map(member => <li>{user}</li>)}
</ul>
</div>
}
}
• does not know anything about redux.
• actions and state are injected in the props … again
• final payload is rendered here
13. sync flow
• Action -> Reducer -> SmartContainer -> DumbComponent
ui
smart Component
injected state
imported
Action Objects
wrapped with
dispatch
redux
Action
Objects / functions
ReducerReducer
Store
dumb Component
references to actions
dumb Component
references to actions
14. function composition
• currying = preconfigured functions being configured a little more
http://stackoverflow.com/a/218055/170881
• checkout lodashs collection of higher order functions
like _.compose, _.curry, _.partial
15. middleware architecture with
functional composition
• It provides a third-party extension point between dispatching an
action, and the moment it reaches the reducer.
• People use Redux middleware for logging, crash reporting, talking
to an asynchronous API, routing, and more.
const logger = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};
let createStoreWithMiddleware = applyMiddleware(logger,...)(createStore);
let myApp = combineReducers(reducers);
let store = createStoreWithMiddleware(myApp);
16. Async Actions• middleware can be used to check if the action contains promises and
can execute them
• In more complex apps you might want to use Rx for resolving async
actions instead, and feed an observable of actions into Redux.
export default function asyncActionsMiddleware(client) {
return ({dispatch, getState}) => {
return next => action => {
const { promise, types, ...rest } = action;
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({...rest, type: REQUEST});
return promise(client).then(
(result) => next({...rest, result, type: SUCCESS}),
(error) => next({...rest, error, type: FAILURE})
).catch((error)=> {
console.error('MIDDLEWARE ERROR:', error);
next({...rest, error, type: FAILURE});
});
};
};
}
17. one Async Flow = two Sync Flows
redux
Action
Objects / functions
ReducerReducer
Store
Middleware for API-
Calls
backend
1. request
1. disposing action for
REQUESTING_DATA
2. response
2. disposing action for
DATA_RECIEVED
ui
smart Component
injected state
imported
Action Objects
wrapped with
dispatch
dumb Component
references to actions
dumb Component
references to actions
1. show spinner
2. show data
API Endpoints
18. react and flux inventory
• 4 Views, 20 react-components and 12 stores.
• one central view with a datagrid where data is shown
• 6 stores in 3 views contribute to the datagrid's display
properties:
• UserRolesStore, datagrid(-DataStore, -FilterStore, -
ContextStore, -SortingStore, -AggregationStore)
• components influence the store but also retrieved data
influences the store and therefore the components as
well.
19. benefits of refactoring to redux
• a single store made sense because all Components contributing to
one major backend call (getData and show in table)
• the stores state is now observed by Rx.Observable for changes and
a new backend call
• Better architecture with more dumb components which have the
properties injected instead of getting them from the store themselves.
• system feels more encapsulated with high cohesion (reducers and
one store) and looser coupling (killing the store calls in the
components)
• functional programming paradigms are enforced by design => easier
unit tests