Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
React+Redux @ Scale
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Daniel Cousineau
June 26, 2017
Programming
360
1
Share
React+Redux @ Scale
Given at QCon NYC 2017
https://qconnewyork.com/ny2017/presentation/reactredux-scale-talk
Daniel Cousineau
June 26, 2017
More Decks by Daniel Cousineau
See All by Daniel Cousineau
Time is a Social Construct
dcousineau
1
670
React @ Scale
dcousineau
0
220
Frontend Performance & You
dcousineau
0
380
Feature Flags & You
dcousineau
2
110
Reframing The Problem - DCJS July 2016
dcousineau
0
150
YAFT
dcousineau
2
160
Queues and the beanstalkd
dcousineau
1
700
How Not Writing PHP Makes You Better At PHP
dcousineau
0
400
JavaScript for PHP Developers
dcousineau
4
720
Other Decks in Programming
See All in Programming
10年分の技術的負債、完済へ ― Claude Code主導のAI駆動開発でスポーツブルを丸ごとリプレイスした話
takuya_houshima
0
2.6k
レガシーPHP転生 〜父がドメインエキスパートだったのでDDD+Claude Codeでチート開発します〜
panda_program
0
980
AI時代のエンジニアリングの原則 / Engineering Principles in the AI Era
haru860
0
350
実用!Hono RPC2026
yodaka
2
230
Vibe NLP for Applied NLP
inesmontani
PRO
0
440
SkillがSkillを生む:QA観点出しを自動化した
sontixyou
6
3.4k
AWS re:Invent 2025の少し振り返り + DevOps AgentとBacklogを連携させてみた
satoshi256kbyte
3
160
The Less-Told Story of Socket Timeouts
coe401_
3
360
ハーネスエンジニアリングにどう向き合うか 〜ルールファイルを超えて開発プロセスを設計する〜 / How to approach harness engineering
rkaga
23
13k
煩雑なSkills管理をSoC(関心の分離)により解決する――関心を分離し、プロンプトを部品として育てるためのOSSを作った話 / Solving Complex Skills Management Through SoC (Separation of Concerns)
nrslib
4
950
CursorとClaudeCodeとCodexとOpenCodeを実際に比較してみた
terisuke
1
470
PCOVから学ぶコードカバレッジ #phpcon_odawara
o0h
PRO
0
270
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
97
6.6k
Test your architecture with Archunit
thirion
1
2.2k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.1k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.2k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.4k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
260
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.2k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
64
55k
We Have a Design System, Now What?
morganepeng
55
8.1k
How Software Deployment tools have changed in the past 20 years
geshan
0
33k
WCS-LA-2024
lcolladotor
0
540
Transcript
React+Redux @ Scale
@dcousineau
None
None
None
None
Rules
None
“Rules”
None
None
Scalability is the capability of a system, network, or process
to handle a growing amount of work, or its potential to be enlarged to accommodate that growth. – Wikipedia
Part 1: React
Rule: Components should be stateless
Reality: State is the enemy, but also inevitable
onClick(e) { const value = e.target.value; const formatted = value.toUpperCase();
this.setState({value: formatted}); }
onClick() { this.setState((previousState, currentProps) => { return { show: !previousState.show,
}; }); }
onClick(e) { this.setState({value: e.target.value}); this.props.onChange(this.state.value); }
onClick(e) { this.setState({value: e.target.value}, () => { this.props.onChange(this.state.value); }); }
Rule: Don’t use Context, it hides complexity
Reality: Sometimes complexity should be hidden
None
None
class TextCard extends React.Component { static contextTypes = { metatypes:
React.PropTypes.object, }; render() { const {cardData} = this.props; const {metatypes} = this.context; return ( <div> The following is either editable or displayed: <metatypes.text value={cardData.text} onChange={this.props.onChange} /> </div> ) } } function selectCardComponent(cardData) { switch (cardData.type) { case 'text': return TextCard; default: throw new Error(`Invalid card type ${cardData.type}`); } }
class TextCard extends React.Component { static contextTypes = { metatypes:
React.PropTypes.object, }; render() { const {cardData} = this.props; const {metatypes} = this.context; return ( <div> The following is either editable or displayed: <metatypes.text value={cardData.text} onChange={this.props.onChange} /> </div> ) } } function selectCardComponent(cardData) { switch (cardData.type) { case 'text': return TextCard; default: throw new Error(`Invalid card type ${cardData.type}`); } }
const metatypesEdit = { text: class extends React.Component { render()
{ return <input type="text" {...this.props} />; } } } const metatypesView = { text: class extends React.Component { render() { return <span>{this.props.value}</span>; } } }
class CardViewer extends React.Component { static childContextTypes = { metatypes:
React.PropTypes.object }; getChildContext() { return {metatypes: metatypesView}; } render() { const {cardData} = this.props; const CardComponent = selectCardComponent(cardData); return <CardComponent cardData={cardData} /> } }
class CardEditor extends React.Component { static childContextTypes = { metatypes:
React.PropTypes.object }; getChildContext() { return {metatypes: metatypesEdit}; } render() { const {cardData} = this.props; const CardComponent = selectCardComponent(cardData); return <CardComponent cardData={cardData} /> } }
Part 2: Redux
Rule: “Single source of truth” means all state in the
store
Reality: You can have multiple “single sources”
this.state.checked = true;
this.props.checked = true; this.props.checked = true; this.props.checked = true; this.state.checked
= true;
this.props.checked = true; this.props.checked = true; this.props.checked = true; this.props.checked
= true; checked: true connect()();
window.location.*
Rule: Side effects should happen outside the Redux cycle
Reality: This doesn’t mean you can’t have callbacks
function persistPostAction(post, callback = () => {}) { return {
type: 'PERSIST_POST', post, callback }; } function *fetchPostsSaga(action) { const status = yield putPostAPI(action.post); yield put(persistPostCompleteAction(status)); yield call(action.callback, status); } class ComposePost extends React.Component { onClickSubmit() { const {dispatch} = this.props; const {post} = this.state; dispatch(persistPostAction(post, () => this.displaySuccessBanner())); } }
class ViewPostPage extends React.Component { componentWillMount() { const {dispatch, postId}
= this.props; dispatch(fetchPostAction(postId, () => this.logPageLoadComplete())); } }
Rule: Redux stores must be normalized for performance
Reality: You must normalize to reduce complexity
https://medium.com/@dcousineau/advanced-redux-entity-normalization-f5f1fe2aefc5
{ byId: { ...entities }, keyWindows: [`${keyWindowName}`], [keyWindowName]: { ids:
['id0', ..., 'idN'], ...meta } }
{ byId: { 'a': userA, 'b': userB, 'c': userC, 'd':
userD }, keyWindows: ['browseUsers', 'allManagers'], browseUsers: { ids: ['a', 'b', 'c'], isFetching: false, page: 1, totalPages: 10, next: '/users?page=2', last: '/users?page=10' }, allManagers: { ids: ['d', 'a'], isFetching: false } }
function selectUserById(store, userId) { return store.users.byId[userId]; } function selectUsersByKeyWindow(store, keyWindow)
{ return store.users[keyWindow].ids.map(userId => selectUserById(store, userId)); }
function fetchUsers({query}, keyWindow) { return { type: FETCH_USERS, query, keyWindow
}; } function fetchManagers() { return fetchUsers({query: {isManager: true}}, 'allManager'); } function receiveEntities(entities, keyWindow) { return { type: RECEIVE_ENTITIES, entities, keyWindow }; }
function reducer(state = defaultState, action) { switch(action.type) { case FETCH_USERS:
return { ...state, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: true, query: action.query } }; case RECEIVE_ENTITIES: return { ...state, byId: { ...state.byId, ...action.entities.users.byId }, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: false, ids: action.entities.users.ids } }; } }
function reducer(state = defaultState, action) { switch(action.type) { case FETCH_USERS:
return { ...state, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: true, query: action.query } }; case RECEIVE_ENTITIES: return { ...state, byId: { ...state.byId, ...action.entities.users.byId }, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: false, ids: action.entities.users.ids } }; } }
function selectUsersAreFetching(store, keyWindow) { return !!store.users[keyWindow].isFetching; } function selectManagersAreFetching(store) {
return selectUsersAreFetching(store, 'allManagers'); }
function reducer(state = defaultState, action) { switch(action.type) { case UPDATE_USER:
return { ...state, draftsById: { ...state.draftsById, [action.user.id]: action.user } }; case RECEIVE_ENTITIES: return { ...state, byId: { ...state.byId, ...action.entities.users.byId }, draftsById: { ...omit(state.draftsById, action.entities.users.byId) }, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: false, ids: action.entities.users.ids } }; } }
function reducer(state = defaultState, action) { switch(action.type) { case UPDATE_USER:
return { ...state, draftsById: { ...state.draftsById, [action.user.id]: action.user } }; case RECEIVE_ENTITIES: return { ...state, byId: { ...state.byId, ...action.entities.users.byId }, draftsById: { ...omit(state.draftsById, action.entities.users.byId) }, keyWindows: uniq([...state.keyWindows, action.keyWindow]), [action.keyWindow]: { ...state[action.keyWindow], isFetching: false, ids: action.entities.users.ids } }; } }
function selectUserById(store, userId) { return store.users.draftsById[userId] || store.users.byId[userId]; }
function reducer(state = defaultState, action) { switch(action.type) { case UNDO_UPDATE_USER:
return { ...state, draftsById: { ...omit(state.draftsById, action.user.id), } }; } }
Part 3: Scale
Rule: Keep dependencies low to keep the application fast
Reality: Use bundling to increase PERCEIVED performance
class Routes extends React.Component { render() { return ( <Switch>
<Route exact path="/" component={require(‘../home').default} /> <Route path="/admin" component={lazy(require(‘bundle-loader?lazy&name=admin!../admin’))} /> <Route component={PageNotFound} /> </Switch> ); } }
require('bundle-loader?lazy&name=admin!../admin’)
const lazy = loader => class extends React.Component { componentWillMount()
{ loader(mod => this.setState({ Component: mod.default ? mod.default : mod }) ); } render() { const { Component } = this.state; if (Component !== null) { return <Component {...this.props} />; } else { return <div>Is Loading!</div>; } } };
None
Rule: Render up-to-date data
Reality: If you got something render it, update it later
None
None
None
None
None
None
Epilog: Scale?
Rule: Scale is bytes served, users concurrent
Reality: Scale is responding to bytes served and users concurrent
How fast can you deploy?
None
Pre: Clear homebrew & yarn caches 1. Reinstall node &
yarn via brew 2. Clone repo 3. Run yarn install 4. Run production build 1. Compile & Minify CSS 2. Compile Server via Babel 3. Compile, Minify, & Gzip via Webpack 190.64s ~3 min
<Feature name="new-feature" fallback={<OldFeatureComponent />}> <NewFeatureComponent /> </Feature>
None
Team 1 Team 2 Merge Feature A Merge Feature B
Deploy Deploy OMG ROLLBACK DEPLOY!!! Merge Feature C Merge Bugfix for A Deploy Deploy BLOCKED!!! Deploy
Team 1 Team 2 Merge Feature A Merge Feature B
Deploy Deploy Rollout Flag A Rollout Flag B OMG ROLLBACK FLAG A!!! Merge Feature C Deploy Merge Bugfix for A Deploy Rollout Flag A Rollout Flag C
Can you optimize your directory structure around team responsibilities? If
teams are organized by “product domain”, Can you organize code around product domain?
Final Thoughts
Strict rules rarely 100% apply to your application. Remembering the
purpose behind the rules is valuable.
Code behavior should be predictable and intuitable. Be realistic about
the problem you’re actually solving.
You will not get it perfect the first time. Optimize
your processes for refactoring.
Questions?