Upgrade to Pro — share decks privately, control downloads, hide ads and more …

In Pursuit of an Ideal Architecture

In Pursuit of an Ideal Architecture

The necessity to keep our code readable and reusable grows as apps become more complex. In this talk, I will describe our journey in pursuit of an ideal javascript architecture from MVC to our innovative approach to deal with data on client-side applications.

More Decks by Milson Ramos de Carvalho Júnior

Other Decks in Programming

Transcript

  1. • Use case; • Presentational and Container Components; • Being

    Functional (Recompose/Ramda); • Elm Architecture Inspiration; • how we’ve been written react components today.
  2. // Dependencies import { Fragment, Component } from 'react'; import

    ContentSection from 'components/ContentSection'; import FilterWrapper from 'components/ChooseYourCard/FilterWrapper'; import Card from 'components/ChooseYourCard/Card'; import { connect } from 'react-redux'; import { fetchData } from 'ducks/requests'; import { cards, incomes } from 'mock/chooseYourCard'; import './ChooseYourCard.styl';
  3. // Component Definition class ChooseYourCard extends Component { constructor(props) {

    super(props); this.state = { incomes: [], currentCard: {}, isLoading: true }; this.onItemClick = this.onItemClick.bind(this); }
  4. // Data const mapDispatchToProps = (dispatch) => { return {

    getIncomes() { const mapper = (res) => { return Object.keys(res).reduce((acc, id) => { const currentIncome = res[id]; return [ ...acc, { id, ...currentIncome } ]; }, []); }; return dispatch(fetchData({ url: '/incomes', key: 'incomes', mock: incomes, mapper })); },
  5. // Controller method onItemClick(e) { e.preventDefault(); const { getCardByIncome }

    = this.props; const id = e.target.dataset.id; getCardByIncome(id) .then((currentCard) => this.setState({ currentCard }, () => { this.setActiveIncome(id); })) }
  6. // Lifecycle method componentDidMount() { const { getIncomes, getCardByIncome }

    = this.props; getIncomes() .then(incomes => { this.setState({ incomes }, () => { const activeIncomeId = this.getActiveIncome(incomes).id; getCardByIncome(activeIncomeId) .then((currentCard) => this.setState({ currentCard }, () => { this.setState({ isLoading: false }) })) }) }) }
  7. • Use case; • Presentational and Container Components; • Being

    Functional (Recompose/Ramda); • Elm Architecture Inspiration; • How we’ve been written react components today.
  8. // FilterWrapper Component <ul className="filter-wrapper"> {incomes.map(({ active, id, value })

    => { return ( <li className="single-option" key={`${id}_${value}`}> <a data-id={id} onClick={this.onItemClick} className={`link${active ? ' -is-active' : ''}`} href="#"> {value} </a> </li> ); })} </ul>
  9. // Card Component <div className="featured-card"> <img src={`/images/${currentCard.banner}`} alt="The best card

    for you!" /> <h2 className="title">{currentCard.name}</h2> <p className="description"> {currentCard.description} </p> <a className="button" href="#">Apply now!</a> </div>
  10. <section className="content-section" data-section="choose-your-card"> <h1 className="title">The best credit card for you!</h1>

    <div className="holder"> { isLoading ? <span className="loader">...Loading</span> : <Fragment> <ul className="filter-wrapper"> {incomes.map(({ active, id, value }) => { return ( <li className="single-option" key={`${id}_${value}`}> <a data-id={id} onClick={this.onItemClick} className={`link${active ? ' -is-active' : ''}`} href="#"> {value} ...
  11. componentDidMount() { const { getIncomes, getCardByIncome } = this.props; getIncomes()

    .then(incomes => { this.setState({ incomes }, () => { const activeIncomeId = this.getActiveIncome(incomes).id; getCardByIncome(activeIncomeId) .then((currentCard) => this.setState({ currentCard }, () => { this.setState({ isLoading: false }) })) }) }) } ...
  12. const ChooseYourCard = ({ isLoading, incomes, onItemClick, currentCard }) =>

    { return ( <ContentSection section="choose-your-card" title="The best credit card for you!"> { isLoading ? <span className="loader">...Loading</span> : <Fragment> <FilterWrapper data={incomes} onItemClick={onItemClick} /> <Card { ...currentCard } /> </Fragment> } </ContentSection> ); };
  13. render() { const { incomes, currentCard, isLoading } = this.state;

    return ( <ChooseYourCard isLoading={isLoading} incomes={incomes} onItemClick={this.onItemClick} currentCard={currentCard} /> ); }
  14. • Use case; • Presentational and Container Components; • Being

    Functional (Recompose/Ramda); • Elm Architecture Inspiration; • How we’ve been written react components today.
  15. class ChooseYourCardContainer extends Component { constructor(props) { super(props); this.state =

    { incomes: [], currentCard: {}, isLoading: true }; this.onItemClick = this.onItemClick.bind(this); }
  16. // Class import { Component } from 'react'; class ChooseYourCardContainer

    extends Component { constructor(props) { super(props); this.state = {}; } }; // Recompose import { compose } from 'recompose'; const ChooseYourCardEnhancer = compose();
  17. // State this.state = { incomes: [], currentCard: {}, isLoading:

    true }; ... this.setState({ isLoading: false }); // Recompose withState utility withState('isLoading', 'setIsLoading', true) ... setIsLoading(false);
  18. // Props onlyUpdateForPropTypes, setPropTypes({ incomes: array, currentCard: object, isLoading: bool,

    getIncomes: func, getCardByIncome: func, onItemClick: func, setActiveIncome: func, getActiveIncome: func });
  19. const ChooseYourCardEnhancer = compose( withState('customIncomes', 'setCustomIncomes', null), withState('currentCard', 'setCurrentCard', {}),

    withState('isLoading', 'setIsLoading', true), connect( mapStateToProps, mapDispatchToProps ), withHandlers({ getActiveIncome: () => (incomes) => incomes.find(({ active }) => active), setActiveIncome: ({ incomes, setCustomIncomes }) => (incomeId) => { ...
  20. setActiveIncome: ({ incomes, setCustomIncomes }) => (incomeId) => { const

    newIncomes = incomes.map((income) => { const { id } = income; if (id === incomeId) { return { ...income, active: true } } return { ...income, active: false }; }) setCustomIncomes(newIncomes); }
  21. setActiveIncome: ({ incomes, setCustomIncomes }) => (incomeId) => { const

    setActiveAs = R.assoc('active'); const updateCollection = R.ifElse( R.propEq('id', incomeId), setActiveAs(true), setActiveAs(false) ); setCustomIncomes(R.map(updateCollection, incomes)); }
  22. const mapStateToProps = ({ requests }) => { const incomes

    = requests && requests.incomes ? requests.incomes.data : []; return { incomes }; };
  23. const mapStateToProps = ({ requests }) => { const incomes

    = R.pathOr([], ['incomes', 'data'], requests); return { incomes }; };
  24. • Use case; • Presentational and Container Components; • Being

    Functional (Recompose/Ramda); • Elm Architecture Inspiration; • How we’ve been written react components today.
  25. import Browser import Html exposing (Html, button, div, text) import

    Html.Events exposing (onClick) main = Browser.sandbox { init = 0, update = update, view = view } type Msg = Increment | Decrement update msg model = case msg of Increment -> model + 1 Decrement -> model - 1 view model = div [] [ button [ onClick Decrement ] [ text "-" ] , div [] [ text (String.fromInt model) ] , button [ onClick Increment ] [ text "+" ] ]
  26. • Use case; • Presentational and Container Components; • Being

    Functional (Recompose/Ramda); • Elm Architecture Inspiration; • How we’ve been written react components today.
  27. import { compose } from 'recompose'; import state from './state';

    import data from './data'; import handlers from './handlers'; import effects from './effects'; import propsMapper from './propsMapper'; export default compose( state, data, handlers, effects, propsMapper );
  28. import { compose, withState } from 'recompose'; export default compose(

    withState('customIncomes', 'setCustomIncomes', null), withState('currentCard', 'setCurrentCard', {}), withState('isLoading', 'setIsLoading', true) );
  29. withHandlers({ onItemClick: ({ getCardByIncome, setActiveIncome, setCurrentCard }) => (e) =>

    { e.preventDefault(); const id = e.target.dataset.id; getCardByIncome(id) .then((currentCard) => { setCurrentCard(currentCard); setActiveIncome(id); }) } }),
  30. export default compose( lifecycle({ componentDidMount() { const { getIncomes, getCardByIncome,

    setCurrentCard, setIsLoading, getActiveIncome } = this.props; getIncomes() .then(incomes => { const activeIncomeId = getActiveIncome(incomes).id; ...
  31. /* Using Recompose */ import { compose } from 'recompose';

    import state from './state'; import data from './data'; import handlers from './handlers'; import effects from './effects'; import propsMapper from './propsMapper'; export default compose( state, data, handlers, effects, propsMapper ); /* Using Custom Hook */ import state from './state'; import data from './data'; import handlers from './handlers'; import effects from './effects'; import propsMapper from './propsMapper'; export default { state, data, handlers, effects, propsMapper };