Successfully reported this slideshow.
Your SlideShare is downloading. ×

Migrating from Flux to Redux. Why and how.

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
Rxjs marble-testing
Rxjs marble-testing
Loading in …3
×

Check these out next

1 of 80 Ad

Migrating from Flux to Redux. Why and how.

Download to read offline

When I started to work with React back in Apr-2015 there were many libraries to manage the application flow. I decided to start with classical FB's Flux implementation to understand what's missed there. Eventually react-redux and redux solved most of issues I had with Flux. This talk is about practical aspects of migration from Flux to Redux.

When I started to work with React back in Apr-2015 there were many libraries to manage the application flow. I decided to start with classical FB's Flux implementation to understand what's missed there. Eventually react-redux and redux solved most of issues I had with Flux. This talk is about practical aspects of migration from Flux to Redux.

Advertisement
Advertisement

More Related Content

Slideshows for you (19)

Advertisement

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

Advertisement

Recently uploaded (20)

Migrating from Flux to Redux. Why and how.

  1. 1. Migrating from Flux to Redux Boris Nadion boris@astrails.com @borisnadion why and how
  2. 2. astrails awesome web apps since 2005
  3. 3. flux? redux?
  4. 4. Apr-2015
  5. 5. backbone and marionette
  6. 6. React - breath of fresh air
  7. 7. best thing since jQuery
  8. 8. spaghetti -> MVC
  9. 9. V = React
  10. 10. M+C = ?
  11. 11. obviously Flux
  12. 12. npm install flux --save
  13. 13. hold on! is there anything better?
  14. 14. hell yeah!
  15. 15. lummox Alt Flexor Flux This MartyJS McFly Flexible Delores Lux Reflux OmniscientJS Fluffy Material Flux …
  16. 16. each one of them was better …then any other
  17. 17. reflux is better than flux because […]
  18. 18. Flummox is better than flux because […]
  19. 19. alt is better than flux because […]
  20. 20. Flux from FB
  21. 21. npm install flux --save
  22. 22. Action Dispatcher Store Action View API view action server action
  23. 23. useless dashboard code review
  24. 24. https://github.com/astrails/react- presentation-flux-redux tag: #flux
  25. 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. 26. sync state <- store
  27. 27. direct access to actions and store
  28. 28. image: http://www.tech-dojo.org/?_escaped_fragment_=/articles/564326afd862ff0b00ef3cc9
  29. 29. smart and dumb
  30. 30. 1 render() { 2 const allYouNeedIsHere = ...; // functions and data 3 return ( 4 <DumpComponent { ...allYouNeedIsHere } /> 5 ); 6 }
  31. 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. 32. maintain its own state but don’t access actions and store
  33. 33. indeed reusable
  34. 34. less talk, more work
  35. 35. refactor actions
  36. 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. 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 };
  38. 38. refactor components
  39. 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. 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. 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. 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. 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
  44. 44. options?
  45. 45. mix-ins aren't they dead?
  46. 46. Mixins Are Dead. Long Live Composition https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order- components-94a0d2f9e750
  47. 47. react-redux aware of components and flow control
  48. 48. redux single source of truth state is read-only changes are made with pure functions x!
  49. 49. Action Dispatcher Action View API view action server action Store
  50. 50. Store View Action API D
  51. 51. http://www.keepcalm-o-matic.co.uk/p/keep-calm-and-let-the-fun-begin-4/
  52. 52. npm i redux --save npm i react-redux —save npm i redux-thunk —save npm i redux-logger --save
  53. 53. containers and components define some terms
  54. 54. components (presentational components), have no idea about redux
  55. 55. containers aware of redux, have no own React code
  56. 56. container = smart component = dumb (less smart)
  57. 57. refactoring plan api -> better api ;-) actions -> actions stores -> reducers components & containers
  58. 58. API
  59. 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
  60. 60. actions
  61. 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
  62. 62. reducers
  63. 63. 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; }
  64. 64. 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 } }) });
  65. 65. creating store
  66. 66. 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; };
  67. 67. components & containers
  68. 68. 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> );
  69. 69. 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> );
  70. 70. https://github.com/astrails/react- presentation-flux-redux tag: #redux
  71. 71. ▾ 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
  72. 72. adding propTypes kinda documentation you’ll use
  73. 73. "react/prop-types": 2,
  74. 74. 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 }; //… }
  75. 75. https://flic.kr/p/7EsetZ newsItem: PropTypes.object
  76. 76. newsItem: PropTypes.shape({…})
  77. 77. https://github.com/astrails/react- presentation-flux-redux branch: master
  78. 78. http://io9.gizmodo.com/5794957/see-darth-vader-riding-a-unicorn-it-is-your-destiny/ thanks!
  79. 79. Q&A Boris Nadion http://astrails.com

×