SlideShare a Scribd company logo
1 of 80
Download to read offline
Migrating from Flux to Redux
Boris Nadion
boris@astrails.com
@borisnadion
why and how
astrails
awesome web apps since 2005
flux? redux?
Apr-2015
backbone and marionette
React - breath of fresh air
best thing since jQuery
spaghetti -> MVC
V = React
M+C = ?
obviously Flux
npm install flux --save
hold on!
is there anything better?
hell yeah!
lummox
Alt
Flexor
Flux This
MartyJS
McFly
Flexible
Delores
Lux
Reflux
OmniscientJS
Fluffy
Material Flux
…
each one of them was better
…then any other
reflux is better than flux because […]
Flummox is better than flux because […]
alt is better than flux because […]
Flux from FB
npm install flux --save
Action Dispatcher Store
Action
View
API
view action
server action
useless dashboard
code review
https://github.com/astrails/react-
presentation-flux-redux
tag: #flux
1 export default class HackerNews extends Component {
2 constructor() {
3 super(...arguments);
4 this.state = HackerNewsStore.getNews();
5 this.onNewsUpdated = this.onNewsUpdated.bind(this);
6 }
7
12 componentWillMount() {
13 HackerNewsStore.addChangeListener(this.onNewsUpdated);
14 }
15
16 componentWillUnmount() {
17 HackerNewsStore.removeChangeListener(this.onNewsUpdated);
18 }
19
20 onNewsUpdated() {
21 this.setState(HackerNewsStore.getNews());
22 }
1 export default class Locations extends Component
2 constructor() {
3 super(...arguments);
4 this.state = LocationsStore.getLocations();
5 this.onLocationsChanged = this.onLocationsCha
6 }
7
8 componentWillMount() {
9 LocationsStore.addChangeListener(this.onLocat
10 }
11
12 componentWillUnmount() {
13 LocationsStore.removeChangeListener(this.onLo
14 }
15
16 onLocationsChanged() {
17 this.setState(LocationsStore.getLocations());
18 }
duplication
sync state <- store
direct access to actions and store
image: http://www.tech-dojo.org/?_escaped_fragment_=/articles/564326afd862ff0b00ef3cc9
smart and dumb
1 render() {
2 const allYouNeedIsHere = ...; // functions and data
3 return (
4 <DumpComponent { ...allYouNeedIsHere } />
5 );
6 }
// const NewsEntry = (props, context) => {};
const NewsEntry = ({ id, title }) => {
if (title) {
return (
<div>
<a href={ `...${id}` } target="_blank">{ title }</a>
</div>
);
}
return (
<div>Loading entry data...</div>
);
};
pure function
maintain its own state
but don’t access actions and store
indeed reusable
less talk, more work
refactor actions
1 const FormActions = {
2 addLocation: (location) => {
3 handleViewAction({
4 type: Constants.Form.ADD_LOCATION,
5 location
6 });
7 RemoteApi.queryTheWeather(location);
8 }
9 };
1 export const addLocation = (location) => {
2 handleViewAction({
3 type: Constants.Form.ADD_LOCATION,
4 location
5 });
6 RemoteApi.queryTheWeather(location);
7 };
1 const LocationActions = {
2 refresh: (location) => {
3 handleViewAction({ type:
Constants.Location.REFRESHING, location });
4 RemoteApi.queryTheWeather(location);
5 },
6
7 querySuccess: (data, location) => {
8 handleServerAction({
9 type:
Constants.Location.QUERY_SUCCESS,
10 data,
11 location
12 });
13 },
14
15 queryFailed: (error) => {
16 handleServerAction({
17 type: Constants.Location.QUERY_FAILED,
18 error
19 });
20 },
21
22 remove: (locationID) => {
23 handleViewAction({ type:
Constants.Location.REMOVE, locationID });
24 }
25 };
1 export const refresh = (location) => {
2 handleViewAction({ type: Constants.Location.RE
3 RemoteApi.queryTheWeather(location);
4 };
5
6 export const remove = (locationID) => {
7 handleViewAction({ type: Constants.Location.RE
8 };
9
10 export const querySuccess = (data, location) =>
11 handleServerAction({
12 type: Constants.Location.QUERY_SUCCESS,
13 data,
14 location
15 });
16 };
17
18 export const queryFailed = (error) => {
19 handleServerAction({
20 type: Constants.Location.QUERY_FAILED,
21 error
22 });
23 };
refactor components
1 const Home = () => (
2 <div className={ classes.home }>
3 <h2>Useless dashboard, flux version</h2>
4 <Form />
5 <Locations />
6 <HackerNews />
7 </div>
8 );
1 const Home = () => (
2 <div className={ classes.home }>
3 <h2>Useless dashboard, flux version</h2>
4 <Weather />
5 <HackerNews />
6 </div>
7 );
1 import { addLocation } from 'fluxx/actions/form';
2 import { refresh, remove } from 'fluxx/actions/location';
3
4 const Weather = () => (
5 <div>
6 <Form addLocation={ addLocation } />
7 <Locations remove={ remove } refresh={ refresh } />
8 </div>
9 );
export default class Locations extends Component {
static propTypes = {
remove: PropTypes.func.isRequired,
refresh: PropTypes.func.isRequired
};
//…
render() {
return (
<div className={ classes.locations }>
{ this.state.locations.map(location => <Location { ...this.props } { ...location } key={ location.uniqId } />) }
</div>
);
}
}
import React from 'react';
import classes from './location.sass';
const Location = ({ remove, refresh, status, temperature, icon_url: iconUrl, text, location, uniqId }) => {
const removeLocation = (e) => {
e.preventDefault();
remove(uniqId);
};
const refreshLocation = (e) => { /* … */ };
const renderState = () => { /* … */ };
return (
<div className={ classes.location }>
<a className={ classes.remove } href="#" onClick={ removeLocation }>&times;</a>
<div className={ classes.name }>
{ location }
</div>
{ renderState() }
</div>
);
};
export default Location;
1 export default class HackerNews extends Component {
2 constructor() {
3 super(...arguments);
4 this.state = HackerNewsStore.getNews();
5 this.onNewsUpdated = this.onNewsUpdated.bind(this);
6 }
7
12 componentWillMount() {
13 HackerNewsStore.addChangeListener(this.onNewsUpdated);
14 }
15
16 componentWillUnmount() {
17 HackerNewsStore.removeChangeListener(this.onNewsUpdated);
18 }
19
20 onNewsUpdated() {
21 this.setState(HackerNewsStore.getNews());
22 }
1 export default class Locations extends Component
2 constructor() {
3 super(...arguments);
4 this.state = LocationsStore.getLocations();
5 this.onLocationsChanged = this.onLocationsCha
6 }
7
8 componentWillMount() {
9 LocationsStore.addChangeListener(this.onLocat
10 }
11
12 componentWillUnmount() {
13 LocationsStore.removeChangeListener(this.onLo
14 }
15
16 onLocationsChanged() {
17 this.setState(LocationsStore.getLocations());
18 }
this shit
options?
mix-ins
aren't they dead?
Mixins Are Dead. Long Live Composition
https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-
components-94a0d2f9e750
react-redux
aware of components and flow control
redux
single source of truth
state is read-only
changes are made with pure functions
x!
Action Dispatcher
Action
View
API
view action
server action
Store
Store View
Action
API
D
http://www.keepcalm-o-matic.co.uk/p/keep-calm-and-let-the-fun-begin-4/
npm i redux --save
npm i react-redux —save
npm i redux-thunk —save
npm i redux-logger --save
containers and components
define some terms
components
(presentational components), have no idea about redux
containers
aware of redux, have no own React code
container = smart
component = dumb (less smart)
refactoring plan
api -> better api ;-)
actions -> actions
stores -> reducers
components & containers
API
import { querySuccess, queryFailed } from 'fluxx/actions/location';
queryTheWeather: (location) => {
SuperAgent.get(`…${location}&APPID=${APIKEY}`).set(‘Accept’, 'application/json').
end((error, res) => {
if (isSuccess(res)) {
querySuccess(res.body, location);
} else {
queryFailed(res);
}
});
},
queryTheWeather: ({ {location}, onSuccess, onFailure }) => {
SuperAgent.get(`…${location}&APPID=${APIKEY}`).set(‘Accept’, 'application/json').
end((error, res) => {
if (isSuccess(res)) {
onSuccess(res.body);
} else {
onFailure(res);
}
});
},
not aware of actions
actions
const querySuccess = (data, location) => ({ type: Constants.Location.QUERY_SUCCESS, data, loca
const queryFailed = (error) => ({ type: Constants.Location.QUERY_FAILED, error });
const startAddingLocation = (location) => ({ type: Constants.Form.ADD_LOCATION, location });
export const addLocation = (location) => (dispatch) => {
dispatch(startAddingLocation(location));
RemoteApi.queryTheWeather({
payload: { location },
onSuccess: (data) => {
dispatch(querySuccess(data, location));
},
onFailure: (error) => {
dispatch(queryFailed(error));
}
});
};
for each async action, usually there are 3 sync actions:
starting the request (update the UI)
request succeed
request failed
reducers
const locations = createReducers(DEFAULT_STATE, {
[Constants.Form.ADD_LOCATION]: (state, action) => {
const { location } = action;
// FIXME: yeah, need a better uniq id
return [...state, { location, uniqId: location, status: LOOKING_OUTSIDE }];
},
[Constants.Location.QUERY_SUCCESS]: (state, action) => updateState(state, acti
[Constants.Location.REFRESHING]: (state, action) => updateState(state, action.
[Constants.Location.REMOVE]: (state, action) => removeLocation(state, action.l
// so what, do your own error handling ;-)
[Constants.Location.QUERY_FAILED]: (state) => state
});
switch (action.type) {
case Constants.Form.ADD_LOCATION:
// FIXME: yeah, need a better uniq id
locations.push({
location: action.location,
status: "Looking outside...",
uniqId: action.location
});
return LocationsStore.emitChange();
case Constants.Location.QUERY_SUCCESS:
updateLocation(action.location, action.data);
return saveChanges();
case Constants.Location.REMOVE:
removeLocation(action.locationID);
return saveChanges();
case Constants.Location.REFRESHING:
updateState(action.location, "Refreshing data...");
return saveChanges();
default:
return true;
}
switch (action.type) {
case Constants.HackerNews.TOP_IDS_LOADED:
newsIds = action.ids;
return HackerNewsStore.emitChange();
case Constants.HackerNews.STORY_FETCHED:
news[action.id] = action.data;
return HackerNewsStore.emitChange();
default:
return true;
}
const hackerNews = createReducers(DEFAULT_STATE, {
[Constants.HackerNews.TOP_IDS_LOADED]: (state, action) => ({ ...state, newsIds: action.ids }),
[Constants.HackerNews.STORY_FETCHED]: (state, action) => ({ ...state, news: { ...state.news, [action.id]:
action.data } })
});
creating store
const allowDebugging = (process.env.NODE_ENV !== 'production') ||
(localStorage && localStorage.getItem('reactDebug') === 'yes');
export const buildStore = () => {
const rootReducer = combineReducers({ locations, hackerNews });
const devToolsExt = (allowDebugging && window.devToolsExtension) ?
window.devToolsExtension() :
f => f;
const middleWare = allowDebugging ?
applyMiddleware(thunkMiddleware, createLogger()) :
applyMiddleware(thunkMiddleware);
const store = createStore(
rootReducer,
undefined,
compose(middleWare, devToolsExt)
);
return store;
};
components & containers
import HackerNews from 'containers/hacker_news';
import Weather from 'containers/weather';
const store = buildStore();
const Home = () => (
<Provider store={ store }>
<div className={ classes.home }>
<h2>Useless dashboard, redux version</h2>
<Weather />
<HackerNews />
</div>
</Provider>
);
import { addLocation, refresh, remove } from 'reduxx/actions/locations';
import Weather from 'components/weather';
import { connect } from 'react-redux';
const mapDispatchToProps = (dispatch) => ({
addLocation: (location) => dispatch(addLocation(location)),
refresh: (location) => dispatch(refresh(location)),
remove: (location) => dispatch(remove(location))
});
const mapStateToProps = (state) => ({ locations: state.locations });
export default connect(mapStateToProps, mapDispatchToProps)(Weather);
container
component
import React, { PropTypes } from 'react';
import Form from 'components/form';
import Locations from 'components/locations';
const Weather = ({ addLocation, refresh, remove, locations }) => (
<div>
<Form addLocation={ addLocation } />
<Locations remove={ remove } refresh={ refresh } locations={ locations } />
</div>
);
https://github.com/astrails/react-
presentation-flux-redux
tag: #redux
▾ src/
▾ components/
▾ hacker_news/
hacker_news.sass
index.js
▾ home/
home.sass
index.js
▾ location/
index.js
location.sass
▾ locations/
index.js
locations.sass
form.js
news_entry.js
weather.js
▾ containers/
hacker_news.js
weather.js
▾ src/
▾ components/
form.js
hacker_news.js
hacker_news.sass
home.js
home.sass
location.js
location.sass
locations.js
locations.sass
news_entry.js
move files to folders - less mess
adding propTypes
kinda documentation you’ll use
"react/prop-types": 2,
const Location = ({ remove, refresh, status, temperature, icon_url: iconUrl, text, location, uniqId }) => {
//…
};
Location.propTypes = {
remove: PropTypes.func.isRequired,
refresh: PropTypes.func.isRequired,
status: PropTypes.string.isRequired,
temperature: PropTypes.number,
icon_url: PropTypes.string,
text: PropTypes.string,
location: PropTypes.string.isRequired,
uniqId: PropTypes.string.isRequired
};
export default class HackerNews extends Component {
static propTypes = {
hackerNews: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string
})),
fetch: PropTypes.func.isRequired
};
//…
}
https://flic.kr/p/7EsetZ
newsItem: PropTypes.object
newsItem: PropTypes.shape({…})
https://github.com/astrails/react-
presentation-flux-redux
branch: master
http://io9.gizmodo.com/5794957/see-darth-vader-riding-a-unicorn-it-is-your-destiny/
thanks!
Q&A
Boris Nadion
http://astrails.com

More Related Content

What's hot

exportDisabledUsersRemoveMailbox
exportDisabledUsersRemoveMailboxexportDisabledUsersRemoveMailbox
exportDisabledUsersRemoveMailbox
Daniel Gilhousen
 

What's hot (19)

Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
 
Angular mix chrisnoring
Angular mix chrisnoringAngular mix chrisnoring
Angular mix chrisnoring
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 
Nativescript angular
Nativescript angularNativescript angular
Nativescript angular
 
Easy Button
Easy ButtonEasy Button
Easy Button
 
Kotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresKotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan Soares
 
RxJS - 封裝程式的藝術
RxJS - 封裝程式的藝術RxJS - 封裝程式的藝術
RxJS - 封裝程式的藝術
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
 
Introducing Vuex in your project
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your project
 
A clean(er) architecture
A clean(er) architectureA clean(er) architecture
A clean(er) architecture
 
Source Code for Dpilot
Source Code for Dpilot Source Code for Dpilot
Source Code for Dpilot
 
Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
exportDisabledUsersRemoveMailbox
exportDisabledUsersRemoveMailboxexportDisabledUsersRemoveMailbox
exportDisabledUsersRemoveMailbox
 
Firebase ng2 zurich
Firebase ng2 zurichFirebase ng2 zurich
Firebase ng2 zurich
 
Lucene
LuceneLucene
Lucene
 
Quick start with React | DreamLab Academy #2
Quick start with React | DreamLab Academy #2Quick start with React | DreamLab Academy #2
Quick start with React | DreamLab Academy #2
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 

Viewers also liked

Estructuras organizacionales
Estructuras organizacionalesEstructuras organizacionales
Estructuras organizacionales
Pato Reino
 

Viewers also liked (20)

Thinking Reactively
Thinking ReactivelyThinking Reactively
Thinking Reactively
 
Designing applications with Redux
Designing applications with ReduxDesigning applications with Redux
Designing applications with Redux
 
Flux and redux
Flux and reduxFlux and redux
Flux and redux
 
Reducers+flux=redux
Reducers+flux=reduxReducers+flux=redux
Reducers+flux=redux
 
Redux vs Alt
Redux vs AltRedux vs Alt
Redux vs Alt
 
React. Flux. Redux
React. Flux. ReduxReact. Flux. Redux
React. Flux. Redux
 
Windows 7
Windows 7Windows 7
Windows 7
 
Health report
Health reportHealth report
Health report
 
Capitulo 5 de el libro blanco de las tic
Capitulo 5 de el libro blanco de las ticCapitulo 5 de el libro blanco de las tic
Capitulo 5 de el libro blanco de las tic
 
Frd nafs
Frd nafsFrd nafs
Frd nafs
 
Transformaciones de procesos
Transformaciones de procesosTransformaciones de procesos
Transformaciones de procesos
 
The lost ones
The lost onesThe lost ones
The lost ones
 
Intro matlab msantos
Intro matlab msantosIntro matlab msantos
Intro matlab msantos
 
Edgar allan poe
Edgar allan poeEdgar allan poe
Edgar allan poe
 
Estructuras organizacionales
Estructuras organizacionalesEstructuras organizacionales
Estructuras organizacionales
 
Product-Market Growth Charts
Product-Market Growth ChartsProduct-Market Growth Charts
Product-Market Growth Charts
 
004. Working with React component
004. Working with React component004. Working with React component
004. Working with React component
 
3 ficha refuerzo 1º eso
3 ficha  refuerzo    1º   eso3 ficha  refuerzo    1º   eso
3 ficha refuerzo 1º eso
 
17 ficha refuerzo 1º eso
17 ficha   refuerzo  1º  eso17 ficha   refuerzo  1º  eso
17 ficha refuerzo 1º eso
 
G.2014-immuno~ (18.immunologic application-xm)
 G.2014-immuno~ (18.immunologic application-xm) G.2014-immuno~ (18.immunologic application-xm)
G.2014-immuno~ (18.immunologic application-xm)
 

Similar to Migrating from Flux to Redux. Why and how.

Building Smart Async Functions For Mobile
Building Smart Async Functions For MobileBuilding Smart Async Functions For Mobile
Building Smart Async Functions For Mobile
Glan Thomas
 
Avinash Kundaliya: Javascript and WordPress
Avinash Kundaliya: Javascript and WordPressAvinash Kundaliya: Javascript and WordPress
Avinash Kundaliya: Javascript and WordPress
wpnepal
 

Similar to Migrating from Flux to Redux. Why and how. (20)

React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7
 
You will learn RxJS in 2017
You will learn RxJS in 2017You will learn RxJS in 2017
You will learn RxJS in 2017
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
JS Fest 2019. Glenn Reyes. With great power comes great React hooks!
JS Fest 2019. Glenn Reyes. With great power comes great React hooks!JS Fest 2019. Glenn Reyes. With great power comes great React hooks!
JS Fest 2019. Glenn Reyes. With great power comes great React hooks!
 
Higher Order Components and Render Props
Higher Order Components and Render PropsHigher Order Components and Render Props
Higher Order Components and Render Props
 
React redux
React reduxReact redux
React redux
 
Reactive programming with RxJS - ByteConf 2018
Reactive programming with RxJS - ByteConf 2018Reactive programming with RxJS - ByteConf 2018
Reactive programming with RxJS - ByteConf 2018
 
Why react matters
Why react mattersWhy react matters
Why react matters
 
React Native: Developing an app similar to Uber in JavaScript
React Native: Developing an app similar to Uber in JavaScriptReact Native: Developing an app similar to Uber in JavaScript
React Native: Developing an app similar to Uber in JavaScript
 
Building Smart Async Functions For Mobile
Building Smart Async Functions For MobileBuilding Smart Async Functions For Mobile
Building Smart Async Functions For Mobile
 
Avinash Kundaliya: Javascript and WordPress
Avinash Kundaliya: Javascript and WordPressAvinash Kundaliya: Javascript and WordPress
Avinash Kundaliya: Javascript and WordPress
 
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
 
Unit Testing Express and Koa Middleware in ES2015
Unit Testing Express and Koa Middleware in ES2015Unit Testing Express and Koa Middleware in ES2015
Unit Testing Express and Koa Middleware in ES2015
 
React 101
React 101React 101
React 101
 
Object-Oriented JavaScript
Object-Oriented JavaScriptObject-Oriented JavaScript
Object-Oriented JavaScript
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascript
 
Road to react hooks
Road to react hooksRoad to react hooks
Road to react hooks
 
Building complex User Interfaces with Sitecore and React
Building complex User Interfaces with Sitecore and ReactBuilding complex User Interfaces with Sitecore and React
Building complex User Interfaces with Sitecore and React
 
Creating an Uber Clone - Part XV.pdf
Creating an Uber Clone - Part XV.pdfCreating an Uber Clone - Part XV.pdf
Creating an Uber Clone - Part XV.pdf
 
State manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexState manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to Vuex
 

More from Astrails

More from Astrails (12)

Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
 
Accounting For Hackers
Accounting For HackersAccounting For Hackers
Accounting For Hackers
 
Machine Learning: Make Your Ruby Code Smarter
Machine Learning: Make Your Ruby Code SmarterMachine Learning: Make Your Ruby Code Smarter
Machine Learning: Make Your Ruby Code Smarter
 
Engineering esthetics
Engineering estheticsEngineering esthetics
Engineering esthetics
 
Lean Software Development
Lean Software DevelopmentLean Software Development
Lean Software Development
 
RubyMotion: Put your Dreams in Motion with Ruby
RubyMotion: Put your Dreams in Motion with RubyRubyMotion: Put your Dreams in Motion with Ruby
RubyMotion: Put your Dreams in Motion with Ruby
 
WTF is NoSQL
WTF is NoSQLWTF is NoSQL
WTF is NoSQL
 
Cassandra intro
Cassandra introCassandra intro
Cassandra intro
 
Ruby is an Acceptable Lisp
Ruby is an Acceptable LispRuby is an Acceptable Lisp
Ruby is an Acceptable Lisp
 
Ruby is Awesome
Ruby is AwesomeRuby is Awesome
Ruby is Awesome
 
Rails missing features
Rails missing featuresRails missing features
Rails missing features
 
Performance - When, What and How
Performance - When, What and HowPerformance - When, What and How
Performance - When, What and How
 

Recently uploaded

Recently uploaded (20)

Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya HalderCustom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
 
Designing for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastDesigning for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at Comcast
 
Connecting the Dots in Product Design at KAYAK
Connecting the Dots in Product Design at KAYAKConnecting the Dots in Product Design at KAYAK
Connecting the Dots in Product Design at KAYAK
 
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
FDO for Camera, Sensor and Networking Device – Commercial Solutions from VinC...
 
Intro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераIntro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджера
 
Demystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John StaveleyDemystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John Staveley
 
AI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří KarpíšekAI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří Karpíšek
 
Buy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptxBuy Epson EcoTank L3210 Colour Printer Online.pptx
Buy Epson EcoTank L3210 Colour Printer Online.pptx
 
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
 
IESVE for Early Stage Design and Planning
IESVE for Early Stage Design and PlanningIESVE for Early Stage Design and Planning
IESVE for Early Stage Design and Planning
 
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdfThe Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
 
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdfWhere to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
 
THE BEST IPTV in GERMANY for 2024: IPTVreel
THE BEST IPTV in  GERMANY for 2024: IPTVreelTHE BEST IPTV in  GERMANY for 2024: IPTVreel
THE BEST IPTV in GERMANY for 2024: IPTVreel
 
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
Measures in SQL (a talk at SF Distributed Systems meetup, 2024-05-22)
 
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
 
PLAI - Acceleration Program for Generative A.I. Startups
PLAI - Acceleration Program for Generative A.I. StartupsPLAI - Acceleration Program for Generative A.I. Startups
PLAI - Acceleration Program for Generative A.I. Startups
 
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdfLinux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1
 
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdfIntroduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
 
Agentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdfAgentic RAG What it is its types applications and implementation.pdf
Agentic RAG What it is its types applications and implementation.pdf
 

Migrating from Flux to Redux. Why and how.

  • 1. Migrating from Flux to Redux Boris Nadion boris@astrails.com @borisnadion why and how
  • 6. React - breath of fresh air
  • 13. hold on! is there anything better?
  • 16. each one of them was better …then any other
  • 17. reflux is better than flux because […]
  • 18. Flummox is better than flux because […]
  • 19. alt is better than flux because […]
  • 25. 1 export default class HackerNews extends Component { 2 constructor() { 3 super(...arguments); 4 this.state = HackerNewsStore.getNews(); 5 this.onNewsUpdated = this.onNewsUpdated.bind(this); 6 } 7 12 componentWillMount() { 13 HackerNewsStore.addChangeListener(this.onNewsUpdated); 14 } 15 16 componentWillUnmount() { 17 HackerNewsStore.removeChangeListener(this.onNewsUpdated); 18 } 19 20 onNewsUpdated() { 21 this.setState(HackerNewsStore.getNews()); 22 } 1 export default class Locations extends Component 2 constructor() { 3 super(...arguments); 4 this.state = LocationsStore.getLocations(); 5 this.onLocationsChanged = this.onLocationsCha 6 } 7 8 componentWillMount() { 9 LocationsStore.addChangeListener(this.onLocat 10 } 11 12 componentWillUnmount() { 13 LocationsStore.removeChangeListener(this.onLo 14 } 15 16 onLocationsChanged() { 17 this.setState(LocationsStore.getLocations()); 18 } duplication
  • 26. sync state <- store
  • 27. direct access to actions and store
  • 30. 1 render() { 2 const allYouNeedIsHere = ...; // functions and data 3 return ( 4 <DumpComponent { ...allYouNeedIsHere } /> 5 ); 6 }
  • 31. // const NewsEntry = (props, context) => {}; const NewsEntry = ({ id, title }) => { if (title) { return ( <div> <a href={ `...${id}` } target="_blank">{ title }</a> </div> ); } return ( <div>Loading entry data...</div> ); }; pure function
  • 32. maintain its own state but don’t access actions and store
  • 36. 1 const FormActions = { 2 addLocation: (location) => { 3 handleViewAction({ 4 type: Constants.Form.ADD_LOCATION, 5 location 6 }); 7 RemoteApi.queryTheWeather(location); 8 } 9 }; 1 export const addLocation = (location) => { 2 handleViewAction({ 3 type: Constants.Form.ADD_LOCATION, 4 location 5 }); 6 RemoteApi.queryTheWeather(location); 7 };
  • 37. 1 const LocationActions = { 2 refresh: (location) => { 3 handleViewAction({ type: Constants.Location.REFRESHING, location }); 4 RemoteApi.queryTheWeather(location); 5 }, 6 7 querySuccess: (data, location) => { 8 handleServerAction({ 9 type: Constants.Location.QUERY_SUCCESS, 10 data, 11 location 12 }); 13 }, 14 15 queryFailed: (error) => { 16 handleServerAction({ 17 type: Constants.Location.QUERY_FAILED, 18 error 19 }); 20 }, 21 22 remove: (locationID) => { 23 handleViewAction({ type: Constants.Location.REMOVE, locationID }); 24 } 25 }; 1 export const refresh = (location) => { 2 handleViewAction({ type: Constants.Location.RE 3 RemoteApi.queryTheWeather(location); 4 }; 5 6 export const remove = (locationID) => { 7 handleViewAction({ type: Constants.Location.RE 8 }; 9 10 export const querySuccess = (data, location) => 11 handleServerAction({ 12 type: Constants.Location.QUERY_SUCCESS, 13 data, 14 location 15 }); 16 }; 17 18 export const queryFailed = (error) => { 19 handleServerAction({ 20 type: Constants.Location.QUERY_FAILED, 21 error 22 }); 23 };
  • 39. 1 const Home = () => ( 2 <div className={ classes.home }> 3 <h2>Useless dashboard, flux version</h2> 4 <Form /> 5 <Locations /> 6 <HackerNews /> 7 </div> 8 ); 1 const Home = () => ( 2 <div className={ classes.home }> 3 <h2>Useless dashboard, flux version</h2> 4 <Weather /> 5 <HackerNews /> 6 </div> 7 );
  • 40. 1 import { addLocation } from 'fluxx/actions/form'; 2 import { refresh, remove } from 'fluxx/actions/location'; 3 4 const Weather = () => ( 5 <div> 6 <Form addLocation={ addLocation } /> 7 <Locations remove={ remove } refresh={ refresh } /> 8 </div> 9 );
  • 41. export default class Locations extends Component { static propTypes = { remove: PropTypes.func.isRequired, refresh: PropTypes.func.isRequired }; //… render() { return ( <div className={ classes.locations }> { this.state.locations.map(location => <Location { ...this.props } { ...location } key={ location.uniqId } />) } </div> ); } }
  • 42. import React from 'react'; import classes from './location.sass'; const Location = ({ remove, refresh, status, temperature, icon_url: iconUrl, text, location, uniqId }) => { const removeLocation = (e) => { e.preventDefault(); remove(uniqId); }; const refreshLocation = (e) => { /* … */ }; const renderState = () => { /* … */ }; return ( <div className={ classes.location }> <a className={ classes.remove } href="#" onClick={ removeLocation }>&times;</a> <div className={ classes.name }> { location } </div> { renderState() } </div> ); }; export default Location;
  • 43. 1 export default class HackerNews extends Component { 2 constructor() { 3 super(...arguments); 4 this.state = HackerNewsStore.getNews(); 5 this.onNewsUpdated = this.onNewsUpdated.bind(this); 6 } 7 12 componentWillMount() { 13 HackerNewsStore.addChangeListener(this.onNewsUpdated); 14 } 15 16 componentWillUnmount() { 17 HackerNewsStore.removeChangeListener(this.onNewsUpdated); 18 } 19 20 onNewsUpdated() { 21 this.setState(HackerNewsStore.getNews()); 22 } 1 export default class Locations extends Component 2 constructor() { 3 super(...arguments); 4 this.state = LocationsStore.getLocations(); 5 this.onLocationsChanged = this.onLocationsCha 6 } 7 8 componentWillMount() { 9 LocationsStore.addChangeListener(this.onLocat 10 } 11 12 componentWillUnmount() { 13 LocationsStore.removeChangeListener(this.onLo 14 } 15 16 onLocationsChanged() { 17 this.setState(LocationsStore.getLocations()); 18 } this shit
  • 46. Mixins Are Dead. Long Live Composition https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order- components-94a0d2f9e750
  • 47. react-redux aware of components and flow control
  • 48. redux single source of truth state is read-only changes are made with pure functions x!
  • 52. npm i redux --save npm i react-redux —save npm i redux-thunk —save npm i redux-logger --save
  • 55. containers aware of redux, have no own React code
  • 56. container = smart component = dumb (less smart)
  • 57. refactoring plan api -> better api ;-) actions -> actions stores -> reducers components & containers
  • 58. API
  • 59. import { querySuccess, queryFailed } from 'fluxx/actions/location'; queryTheWeather: (location) => { SuperAgent.get(`…${location}&APPID=${APIKEY}`).set(‘Accept’, 'application/json'). end((error, res) => { if (isSuccess(res)) { querySuccess(res.body, location); } else { queryFailed(res); } }); }, queryTheWeather: ({ {location}, onSuccess, onFailure }) => { SuperAgent.get(`…${location}&APPID=${APIKEY}`).set(‘Accept’, 'application/json'). end((error, res) => { if (isSuccess(res)) { onSuccess(res.body); } else { onFailure(res); } }); }, not aware of actions
  • 61. const querySuccess = (data, location) => ({ type: Constants.Location.QUERY_SUCCESS, data, loca const queryFailed = (error) => ({ type: Constants.Location.QUERY_FAILED, error }); const startAddingLocation = (location) => ({ type: Constants.Form.ADD_LOCATION, location }); export const addLocation = (location) => (dispatch) => { dispatch(startAddingLocation(location)); RemoteApi.queryTheWeather({ payload: { location }, onSuccess: (data) => { dispatch(querySuccess(data, location)); }, onFailure: (error) => { dispatch(queryFailed(error)); } }); }; for each async action, usually there are 3 sync actions: starting the request (update the UI) request succeed request failed
  • 63.
  • 64. const locations = createReducers(DEFAULT_STATE, { [Constants.Form.ADD_LOCATION]: (state, action) => { const { location } = action; // FIXME: yeah, need a better uniq id return [...state, { location, uniqId: location, status: LOOKING_OUTSIDE }]; }, [Constants.Location.QUERY_SUCCESS]: (state, action) => updateState(state, acti [Constants.Location.REFRESHING]: (state, action) => updateState(state, action. [Constants.Location.REMOVE]: (state, action) => removeLocation(state, action.l // so what, do your own error handling ;-) [Constants.Location.QUERY_FAILED]: (state) => state }); switch (action.type) { case Constants.Form.ADD_LOCATION: // FIXME: yeah, need a better uniq id locations.push({ location: action.location, status: "Looking outside...", uniqId: action.location }); return LocationsStore.emitChange(); case Constants.Location.QUERY_SUCCESS: updateLocation(action.location, action.data); return saveChanges(); case Constants.Location.REMOVE: removeLocation(action.locationID); return saveChanges(); case Constants.Location.REFRESHING: updateState(action.location, "Refreshing data..."); return saveChanges(); default: return true; }
  • 65. switch (action.type) { case Constants.HackerNews.TOP_IDS_LOADED: newsIds = action.ids; return HackerNewsStore.emitChange(); case Constants.HackerNews.STORY_FETCHED: news[action.id] = action.data; return HackerNewsStore.emitChange(); default: return true; } const hackerNews = createReducers(DEFAULT_STATE, { [Constants.HackerNews.TOP_IDS_LOADED]: (state, action) => ({ ...state, newsIds: action.ids }), [Constants.HackerNews.STORY_FETCHED]: (state, action) => ({ ...state, news: { ...state.news, [action.id]: action.data } }) });
  • 67. const allowDebugging = (process.env.NODE_ENV !== 'production') || (localStorage && localStorage.getItem('reactDebug') === 'yes'); export const buildStore = () => { const rootReducer = combineReducers({ locations, hackerNews }); const devToolsExt = (allowDebugging && window.devToolsExtension) ? window.devToolsExtension() : f => f; const middleWare = allowDebugging ? applyMiddleware(thunkMiddleware, createLogger()) : applyMiddleware(thunkMiddleware); const store = createStore( rootReducer, undefined, compose(middleWare, devToolsExt) ); return store; };
  • 69. import HackerNews from 'containers/hacker_news'; import Weather from 'containers/weather'; const store = buildStore(); const Home = () => ( <Provider store={ store }> <div className={ classes.home }> <h2>Useless dashboard, redux version</h2> <Weather /> <HackerNews /> </div> </Provider> );
  • 70. import { addLocation, refresh, remove } from 'reduxx/actions/locations'; import Weather from 'components/weather'; import { connect } from 'react-redux'; const mapDispatchToProps = (dispatch) => ({ addLocation: (location) => dispatch(addLocation(location)), refresh: (location) => dispatch(refresh(location)), remove: (location) => dispatch(remove(location)) }); const mapStateToProps = (state) => ({ locations: state.locations }); export default connect(mapStateToProps, mapDispatchToProps)(Weather); container component import React, { PropTypes } from 'react'; import Form from 'components/form'; import Locations from 'components/locations'; const Weather = ({ addLocation, refresh, remove, locations }) => ( <div> <Form addLocation={ addLocation } /> <Locations remove={ remove } refresh={ refresh } locations={ locations } /> </div> );
  • 72. ▾ src/ ▾ components/ ▾ hacker_news/ hacker_news.sass index.js ▾ home/ home.sass index.js ▾ location/ index.js location.sass ▾ locations/ index.js locations.sass form.js news_entry.js weather.js ▾ containers/ hacker_news.js weather.js ▾ src/ ▾ components/ form.js hacker_news.js hacker_news.sass home.js home.sass location.js location.sass locations.js locations.sass news_entry.js move files to folders - less mess
  • 75. const Location = ({ remove, refresh, status, temperature, icon_url: iconUrl, text, location, uniqId }) => { //… }; Location.propTypes = { remove: PropTypes.func.isRequired, refresh: PropTypes.func.isRequired, status: PropTypes.string.isRequired, temperature: PropTypes.number, icon_url: PropTypes.string, text: PropTypes.string, location: PropTypes.string.isRequired, uniqId: PropTypes.string.isRequired }; export default class HackerNews extends Component { static propTypes = { hackerNews: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string })), fetch: PropTypes.func.isRequired }; //… }