$30 off During Our Annual Pro Sale. View Details »

Smells in React Apps

Smells in React Apps

Presented at JSConf.Asia 2018

Thai Pangsakulyanont

January 27, 2018
Tweet

More Decks by Thai Pangsakulyanont

Other Decks in Technology

Transcript

  1. Thai Pangsakulyanont
    1
    Smells in React Apps
    JSConf.Asia 2018

    View Slide

  2. Slide № 2

    View Slide

  3. Slide № 3
    Sawasdee krub
    (Hello in Thai)

    View Slide

  4. Slide № 4
    Smells in React Apps

    View Slide

  5. Slide № 5
    Refactoring: Improving the Design of Existing Code
    Clean Code: A Handbook of Agile Software Craftsmanship
    Agile Software Development, Principles, Patterns, and Practices

    View Slide

  6. Slide № 6
    (Contents afterwards are mainly based on my experience.)

    View Slide

  7. About Me
    Slide № 7
    Thai Pangsakulyanont
    (@dtinth)

    View Slide

  8. Slide № 8

    View Slide

  9. Slide № 9
    Loves to build software for fun.

    View Slide

  10. Slide № 10
    dtinth/midi-instruments
    https://302.dt.in.th/midi-instruments-pv

    View Slide

  11. Slide № 11
    dtinth/atom-aesthetic-ui

    View Slide

  12. Slide № 12
    dtinth/atom-aesthetic-ui

    View Slide

  13. Slide № 13
    dtinth/GyroscratchAndroid
    bemusic/gyroscratch-web
    https://302.dt.in.th/gyroscratch-pv

    View Slide

  14. Slide № 14
    Loves to build software for fun.

    View Slide

  15. Slide № 15
    Interested in the ways

    to make software maintainable.

    View Slide

  16. Slide № 16
    One of my old project…
    (2009)

    View Slide

  17. Slide № 17
    7000 lines of untestable code.

    View Slide

  18. Slide № 18
    7000 lines of untestable code.
    Maintain: Not fun

    View Slide

  19. Slide № 19
    7000 lines of untestable code.
    Maintain: Not fun
    Improve the codebase: Not fun

    View Slide

  20. Slide № 20
    Development stopped

    View Slide

  21. Slide № 21
    Development stopped :(

    View Slide

  22. Slide № 22
    What I learned…

    View Slide

  23. Slide № 23
    Sustainability matters!

    View Slide

  24. Slide № 24
    Interested in the ways

    to make software maintainable.

    View Slide

  25. Slide № 25
    Object-oriented analysis and design
    Clean architecture
    Functional programming
    Automated testing
    Continuous integration
    Linting
    Type systems
    Modularization
    Componentization
    Code smells

    View Slide

  26. Slide № 26
    Later… (2014)

    View Slide

  27. Slide № 27
    bemusic/bemuse
    https://302.dt.in.th/bemuse-pv
    ADVERTISEMENT
    https://bemuse.ninja/

    View Slide

  28. Slide № 28

    View Slide

  29. Slide № 29

    View Slide

  30. Slide № 30
    Static analysis with ESLint

    View Slide

  31. Slide № 31
    Static analysis with ESLint

    View Slide

  32. Slide № 32
    Unit testing (Mocha)

    View Slide

  33. Slide № 33
    Continuous integration & deployment (CircleCI)

    View Slide

  34. Slide № 34
    Code coverage (Codecov)

    View Slide

  35. Slide № 35
    3 weeks of project setup

    View Slide

  36. Slide № 36
    Result

    View Slide

  37. Slide № 37
    :)
    Unit tests increases confidence
    when making a huge change to the codebase.

    View Slide

  38. Slide № 38
    Decoupled architecture

    View Slide

  39. Slide № 39
    Implementation details behind an interface

    View Slide

  40. Slide № 40
    Some features can be prototyped in 1 night

    View Slide

  41. Slide № 41
    A good codebase is like a clean table.

    View Slide

  42. Slide № 42

    View Slide

  43. Slide № 43

    View Slide

  44. Slide № 44

    View Slide

  45. Slide № 45
    Present (2018)

    View Slide

  46. Slide № 46
    Front-end architect at @taskworld
    * Opinion expressed are my own.

    View Slide

  47. Slide № 47
    React + Redux

    View Slide

  48. Slide № 48

    View Slide

  49. Slide № 49
    Redux store

    View Slide

  50. Slide № 50
    Redux store React components

    View Slide

  51. Slide № 51
    Redux store React components
    900+ components

    View Slide

  52. Slide № 52
    Redux store React components
    900+ components
    Over 2 years

    View Slide

  53. Slide № 53
    Redux store React components
    900+ components
    Over 2 years, 20+ engineers worked on it

    View Slide

  54. Slide № 54
    Object-oriented analysis and design
    Clean architecture
    Functional programming
    Automated testing
    Continuous integration
    Linting
    Type systems
    Modularization
    Componentization
    Code smells

    View Slide

  55. Slide № 55
    Object-oriented analysis and design
    Clean architecture
    Functional programming
    Automated testing
    Continuous integration
    Linting
    Type systems
    Modularization
    Componentization
    Code smells

    Tech stuffs

    View Slide

  56. Slide № 56
    Communication

    View Slide

  57. Slide № 57
    Example

    View Slide

  58. Slide № 58
    // MusicPreviewer.js
    export function preload () {
    getInstance()
    }
    export function enable () {
    return getInstance().enable()
    }
    export function disable () {
    return getInstance().disable()
    }
    export function go () {
    return getInstance().go()
    }
    export function preview (url) {
    return getInstance().preview(url)
    }
    MusicPreviewer

    View Slide

  59. Slide № 59
    // MusicPreviewer.js
    export function preload () {
    getInstance()
    }
    export function enable () {
    return getInstance().enable()
    }
    export function disable () {
    return getInstance().disable()
    }
    export function go () {
    return getInstance().go()
    }
    export function preview (url) {
    return getInstance().preview(url)
    }
    MusicPreviewer
    exports
    5 functions

    View Slide

  60. Slide № 60
    // MusicPreviewer.js
    export function preload () {
    getInstance()
    }
    export function enable () {
    return getInstance().enable()
    }
    export function disable () {
    return getInstance().disable()
    }
    export function go () {
    return getInstance().go()
    }
    export function preview (url) {
    return getInstance().preview(url)
    }
    import * as MusicPreviewer from
    MusicPreviewer.preload()
    MusicPreviewer.enable()
    MusicPreviewer.disable()
    MusicPreviewer.go()
    MusicPreviewer.preview(url)
    User code:

    View Slide

  61. Slide № 61
    Behind the interface

    View Slide

  62. Slide № 62
    let instance
    const getInstance = () => instance || (
    instance = createMusicPreviewer()
    )
    function createMusicPreviewer () {
    let enabled = false
    let currentUrl = null
    let backgroundLoaded = false
    let backgroundPlayed = false
    const instances = {}
    function update () {
    if (!enabled) {
    if (backgroundPlayed) {
    backgroundFader.fadeTo(0, 100)
    backgroundPlayed = false
    background.pause()
    }
    for (const key of Object.keys(instances)) {
    const instance = instances[key]
    instance.destroy()
    }
    return
    }
    let playing = null
    for (const key of Object.keys(instances)) {
    const instance = instances[key]
    if (key === currentUrl) {
    if (instance.loaded) {
    instance.play()
    playing = instance
    } else {
    instance.stop()
    }
    }
    if (playing) {
    backgroundFader.fadeTo(0, 1)
    } else {
    backgroundFader.fadeTo(0.4, 0.5)
    if (backgroundLoaded && !backgroundPlayed) {
    backgroundPlayed = true
    try {
    background.play()
    } catch (e) {
    console.warn('Cannot play background music')
    }
    }
    }
    }
    const musicPreviewer = {
    enable () {
    if (enabled) return
    enabled = true
    update()
    },
    disable () {
    if (!enabled) return
    enabled = false
    update()
    },
    go () {
    if (!enabled) return
    goSound.currentTime = 0

    View Slide

  63. Slide № 63
    Wrote a hack behind a well-designed interface
    In principle:
    Bad code stays behind that interface.

    View Slide

  64. Slide № 64
    Wrote a hack behind a well-designed interface
    In principle:
    Bad code stays behind that interface.
    In practice:
    Another person looks for a technical solution,

    View Slide

  65. Slide № 65
    Wrote a hack behind a well-designed interface
    In principle:
    Bad code stays behind that interface.
    In practice:
    Another person looks for a technical solution,
    found my hack (but didn’t know it was a hack),

    View Slide

  66. Slide № 66
    Wrote a hack behind a well-designed interface
    In principle:
    Bad code stays behind that interface.
    In practice:
    Another person looks for a technical solution,
    found my hack (but didn’t know it was a hack),
    and duplicated the code!

    View Slide

  67. Slide № 67
    Effective code reviews

    View Slide

  68. Slide № 68
    Smells

    View Slide

  69. Slide № 69
    Information leaks

    View Slide

  70. Slide № 70
    Example

    View Slide

  71. Slide № 71
    Standard UI kit

    View Slide

  72. Slide № 72
    A UserAvatar component

    View Slide

  73. Slide № 73

    A UserAvatar component

    View Slide

  74. Slide № 74


    A UserAvatar component

    View Slide

  75. Slide № 75



    A UserAvatar component

    View Slide

  76. Slide № 76



    A UserAvatar component

    View Slide

  77. Slide № 77
    New feature!
    Messaging system

    View Slide

  78. Slide № 78
    Thai Pangsakulyanont 1 minute ago
    Let’s go to the Marina Bay Carnival
    after the conference!!!!

    View Slide

  79. Slide № 79

    View Slide

  80. Slide № 80



    A UserAvatar component

    View Slide

  81. Slide № 81



    A UserAvatar component

    View Slide

  82. Slide № 82



    A UserAvatar component

    View Slide

  83. Slide № 83



    A UserAvatar component

    View Slide

  84. Slide № 84
    Few months later

    View Slide

  85. Slide № 85
    Few months later
    everywhere!

    View Slide

  86. Slide № 86
    Step out of code
    Look at the big picture

    View Slide

  87. Slide № 87
    UserAvatar

    Message

    View Slide

  88. Slide № 88
    UserAvatar

    specific feature
    Message

    View Slide

  89. Slide № 89
    UserAvatar

    specific feature generic component
    Message

    View Slide

  90. Slide № 90
    UserAvatar

    specific feature generic component
    I need to show an avatar!
    Message

    View Slide

  91. Slide № 91
    UserAvatar
    uses
    specific feature generic component
    Message
    I need to show an avatar!

    View Slide

  92. Slide № 92
    UserAvatar
    uses
    specific feature generic component
    I know how big to display in a message
    Message

    View Slide

  93. Slide № 93
    UserAvatar
    uses
    aware of
    specific feature generic component
    Message
    I know how big to display in a message

    View Slide

  94. Slide № 94
    UserAvatar
    uses
    aware of
    specific feature generic component
    Information leaking.
    Message

    View Slide

  95. Slide № 95
    UserAvatar
    Message
    uses
    aware of
    specific feature generic component
    Information leaking.
    Knows how to render itself
    Except: Doesn’t know the
    appropriate size of the avatar

    View Slide

  96. Slide № 96
    UserAvatar
    Message
    uses
    aware of
    specific feature generic component
    Information leaking.
    Knows how to render itself
    Except: Doesn’t know the
    appropriate size of the avatar
    The knowledge of the size, which
    should have belonged to the
    messaging system is here

    View Slide

  97. Slide № 97
    Leakage
    Information about a specific feature
    leaked into a generic component

    View Slide

  98. Slide № 98
    Generic component not flexible enough.

    View Slide

  99. Slide № 99
    Make the generic component more flexible

    View Slide

  100. Slide № 100



    A UserAvatar component

    View Slide

  101. Slide № 101
    A UserAvatar component
    XS S M L
    XL XXL
    XXXL

    View Slide

  102. Slide № 102
    UserAvatar

    specific feature generic component
    I need to show an avatar! It’s going to have size M!
    Message

    View Slide

  103. Slide № 103
    A UserAvatar component
    function UserAvatar (props) {

    }
    UserAvatar.propTypes = {
    size: PropTypes.oneOf([ 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XX

    }

    View Slide

  104. Slide № 104
    A UserAvatar component
    function UserAvatar (props) {

    }
    UserAvatar.propTypes = {
    size: PropTypes.oneOf([ 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XX

    }

    View Slide

  105. Slide № 105
    A UserAvatar component
    function UserAvatar (props) {

    }
    UserAvatar.propTypes = {
    size: PropTypes.number,

    }

    View Slide

  106. Slide № 106
    A UserAvatar component
    function UserAvatar (props) {

    }
    UserAvatar.propTypes = {
    size: PropTypes.oneOf([ 18, 24, 30, 36, 48, … ]),

    }

    View Slide

  107. Slide № 107
    Sometimes it’s just naming.

    View Slide

  108. Slide № 108
    Example

    View Slide

  109. Slide № 109
    Standard UI kit

    View Slide

  110. Slide № 110
    Upload file

    View Slide

  111. Slide № 111
    Upload file

    View Slide

  112. Slide № 112
    Upload file

    View Slide

  113. Slide № 113
    Upload file
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    View Slide

  114. Slide № 114
    Upload file
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert
    Select file on computer…

    View Slide

  115. Slide № 115
    Upload file
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    View Slide

  116. Slide № 116
    Upload file
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert


    View Slide

  117. Slide № 117
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert
    Upload file

    View Slide

  118. Slide № 118
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    Upload file

    View Slide

  119. Slide № 119
    Upload file

    View Slide

  120. Slide № 120

    Upload file

    View Slide

  121. Slide № 121
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    Upload file

    View Slide

  122. Slide № 122
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    Upload file
    Select file on computer…

    View Slide

  123. Slide № 123
    Select file on computer…
    Dropbox
    Google Drive
    Emergency alert

    Upload file
    Select file on computer…
    Button must appear to be
    pressed while menu is open

    View Slide

  124. Slide № 124
    Upload file

    View Slide

  125. Slide № 125
    Upload file
    Button.propTypes = {
    children: PropTypes.node
    }

    View Slide

  126. Slide № 126
    Upload file
    Button.propTypes = {
    children: PropTypes.node,
    isMenuOpen: PropTypes.bool
    }

    View Slide

  127. Slide № 127
    Button.propTypes = {
    children: PropTypes.node,
    isMenuOpen: PropTypes.bool
    }
    Upload file

    View Slide

  128. Slide № 128
    Button.propTypes = {
    children: PropTypes.node,
    isMenuOpen: PropTypes.bool
    }
    Button is now aware of the menu
    Upload file

    View Slide

  129. Slide № 129
    Button.propTypes = {
    children: PropTypes.node,
    isMenuOpen: PropTypes.bool
    }
    Upload file

    View Slide

  130. Slide № 130
    Button.propTypes = {
    children: PropTypes.node,
    isDepressed: PropTypes.bool
    }
    Upload file

    View Slide

  131. Slide № 131
    When reviewing code:
    “Does this really needs to know about that?”

    View Slide

  132. Slide № 132
    Low cohesion

    View Slide

  133. Slide № 133
    “Cohesion”
    Related things stay together.

    View Slide

  134. Slide № 134
    Example

    View Slide

  135. Slide № 135
    A navigation bar

    View Slide

  136. Slide № 136
    A navigation bar

    View Slide

  137. Slide № 137
    class Navigation extends Component {
    render () {
    }

    View Slide

  138. Slide № 138
    class Navigation extends Component {
    render () {
    return (


    )
    }

    View Slide

  139. Slide № 139
    class Navigation extends Component {
    render () {
    return (



    My app



    )
    }

    View Slide

  140. Slide № 140
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search


    )
    }

    View Slide

  141. Slide № 141
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search


    )
    }

    View Slide

  142. Slide № 142
    New feature!

    View Slide

  143. Slide № 143
    New feature!
    Authentication system

    View Slide

  144. Slide № 144
    When logged out
    When logged in

    View Slide

  145. Slide № 145
    When logged out
    When logged in

    View Slide

  146. Slide № 146
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search


    )
    }
    }

    View Slide

  147. Slide № 147
    class Navigation extends Component {

    render () {
    return (



    My app



    Home
    Explore
    Search

    View Slide

  148. Slide № 148
    class Navigation extends Component {

    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    return (



    My app



    Home
    Explore
    Search

    View Slide

  149. Slide № 149
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search

    View Slide

  150. Slide № 150
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search


    )
    }
    }

    View Slide

  151. Slide № 151
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search




    )
    }
    }

    View Slide

  152. Slide № 152
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search


    {!loggedIn && Login{loggedIn && Logout

    )
    }
    }

    View Slide

  153. Slide № 153
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    View Slide

  154. Slide № 154
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    View Slide

  155. Slide № 155
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(props)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    View Slide

  156. Slide № 156
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(props)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    Selecting data from store

    View Slide

  157. Slide № 157
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(props)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    Selecting data from store
    Dispatching action to store

    View Slide

  158. Slide № 158
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(props)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    Selecting data from store
    Dispatching action to store
    Rendering logic

    View Slide

  159. Slide № 159
    New features!

    View Slide

  160. Slide № 160
    Notification system
    Messaging system

    View Slide

  161. Slide № 161
    When logged out
    When logged in

    View Slide

  162. Slide № 162
    When logged out
    When logged in

    View Slide

  163. Slide № 163
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    View Slide

  164. Slide № 164
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    Selecting data from store
    Dispatching action to store
    Rendering logic

    View Slide

  165. Slide № 165
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    Selecting data from store
    Dispatching action to store
    Rendering logic
    Go with the flow!

    View Slide

  166. Slide № 166
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (


    View Slide

  167. Slide № 167
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser

    View Slide

  168. Slide № 168
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser

    View Slide

  169. Slide № 169
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser

    View Slide

  170. Slide № 170
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore

    View Slide

  171. Slide № 171
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app


    View Slide

  172. Slide № 172
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app


    View Slide

  173. Slide № 173



    My app



    Home
    Explore
    Search


    {!loggedIn && Login{loggedIn && Logout

    )
    }
    }

    View Slide

  174. Slide № 174



    My app



    Home
    Explore
    Search


    View Slide

  175. Slide № 175



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    }


    View Slide

  176. Slide № 176



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    }
    {loggedIn &&

    Messages ({this.props.newMessageCount})

    }


    View Slide

  177. Slide № 177



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    }
    {loggedIn &&

    Messages ({this.props.newMessageCount})

    }


    View Slide

  178. Slide № 178
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    View Slide

  179. Slide № 179
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})
    Selecting data from store
    Dispatching action to store
    Rendering logic

    View Slide

  180. Slide № 180
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})
    Selecting data from store
    Dispatching action to store
    Rendering logic
    Cross-cutting concerns

    View Slide

  181. Slide № 181
    User
    Notification
    Message
    User
    Message
    User
    Notification
    Message
    User
    Navigation
    Navigation
    User
    Navigation
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    View Slide

  182. Slide № 182
    User
    Notification
    Message
    User
    Message
    User
    Notification
    Message
    User
    Navigation
    Navigation
    User
    Navigation
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    View Slide

  183. Slide № 183
    Real-world example

    View Slide

  184. Slide № 184
    Bemuse options panel

    View Slide

  185. Slide № 185
    Bemuse options panel

    View Slide

  186. Slide № 186
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)



    Save & Exit


    }
    })

    View Slide

  187. Slide № 187
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    + ),
    + onToggleGauge: ({ options }) => () => (
    + OptionsIO.setOptions({
    + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge'])
    + })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)



    Save & Exit


    }
    })

    View Slide

  188. Slide № 188
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    + ),
    + onToggleGauge: ({ options }) => () => (
    + OptionsIO.setOptions({
    + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge'])
    + })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    + onToggleGauge: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)



    Save & Exit


    }
    })

    View Slide

  189. Slide № 189
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    + ),
    + onToggleGauge: ({ options }) => () => (
    + OptionsIO.setOptions({
    + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge'])
    + })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    + onToggleGauge: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)



    Save & Exit


    }
    })

    View Slide

  190. Slide № 190
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    + ),
    + onToggleGauge: ({ options }) => () => (
    + OptionsIO.setOptions({
    + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge'])
    + })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    + onToggleGauge: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)


    +
    + + checked={Options.isGaugeEnabled(this.props.options)}
    + onToggle={this.props.onToggleGauge}
    + >
    + Show expert gauge (experimental)
    +
    +
    +

    Save & Exit


    }
    })

    View Slide

  191. Slide № 191
    diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx
    index a2e1d2e..2333c9f 100644
    --- src/app/ui/OptionsPlayer.jsx
    +++ src/app/ui/OptionsPlayer.jsx
    @@ -1,176 +1,191 @@
    const enhance = compose(
    connect((state) => ({
    options: state.options,
    scratch: Options.scratchPosition(state.options),
    })),
    connectIO({
    onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }),
    onSetScratch: () => (position) => OptionsIO.setScratch(position),
    onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed),
    onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime),
    onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover),
    onToggleBackgroundAnimationsEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'system.bga.enabled': Options.toggleOption(options['system.bga.enabled'])
    })
    ),
    onToggleAutoVelocityEnabled: ({ options }) => () => (
    OptionsIO.setOptions({
    'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity'])
    })
    + ),
    + onToggleGauge: ({ options }) => () => (
    + OptionsIO.setOptions({
    + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge'])
    + })
    )
    }),
    pure
    )
    export const OptionsPlayer = React.createClass({
    propTypes: {
    options: React.PropTypes.object,
    scratch: React.PropTypes.string,
    onClose: React.PropTypes.func,
    onSetPanel: React.PropTypes.func,
    onSetScratch: React.PropTypes.func,
    onSetSpeed: React.PropTypes.func,
    onSetLaneCover: React.PropTypes.func,
    onToggleBackgroundAnimationsEnabled: React.PropTypes.func,
    onToggleAutoVelocityEnabled: React.PropTypes.func,
    + onToggleGauge: React.PropTypes.func,
    onSetLeadTime: React.PropTypes.func
    },
    render () {
    return
    label="Speed"
    hidden={Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={this.props.options['player.P1.speed']}
    onChange={this.props.onSetSpeed}
    />

    You can also change the speed in-game
    using the Up and Down arrow keys.


    label="LeadTime"
    hidden={!Options.isAutoVelocityEnabled(this.props.options)}
    >
    value={Options.leadTime(this.props.options)}
    onChange={this.props.onSetLeadTime}
    style={{ width: '5em' }}
    />

    Speed will be automatically adjusted
    to maintain a consistent note velocity.



    options={SCRATCH_OPTIONS}
    onSelect={this.props.onSetScratch}
    value={this.props.scratch}
    />


    options={PANEL_OPTIONS}
    onSelect={this.props.onSetPanel}
    value={this.props.options['player.P1.panel']}
    />


    value={Options.laneCover(this.props.options)}
    onChange={this.props.onSetLaneCover}
    style={{ width: '5em' }}
    />
    className="OptionsPlayerͷhelp"
    title="Can be negative, in this case the play area is pulled up."
    >
    The amount of play area to hide from the top.



    checked={Options.isBackgroundAnimationsEnabled(this.props.options)}
    onToggle={this.props.onToggleBackgroundAnimationsEnabled}
    >
    Enable background animations (720p, alpha)



    checked={Options.isAutoVelocityEnabled(this.props.options)}
    onToggle={this.props.onToggleAutoVelocityEnabled}
    >
    Maintain absolute note velocity (advanced)


    +
    + + checked={Options.isGaugeEnabled(this.props.options)}
    + onToggle={this.props.onToggleGauge}
    + >
    + Show expert gauge (experimental)
    +
    +
    +

    Save & Exit


    }
    })

    View Slide

  192. Slide № 192
    Cohesion loss
    Symptom: Making a simple change
    requires changing code that is further apart.

    View Slide

  193. Slide № 193
    Cohesion loss
    Symptom: Reviewing a PR requires a lot
    of scrolling up and down to understand.

    View Slide

  194. Slide № 194
    components/
    scoreboard/
    ScoreboardView.js
    modules/
    constants.js
    scoreboard/
    actions.js
    reducer.js
    selectors.js
    utils.js

    View Slide

  195. Slide № 195

    View Slide

  196. Slide № 196
    200 files 200 files
    200 files
    500 files

    View Slide

  197. Slide № 197
    Let’s try aiming for functional cohesion
    instead of logical cohesion.

    View Slide

  198. Slide № 198
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func

    View Slide

  199. Slide № 199
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    Remove stuff not directly
    related to Navigation

    View Slide

  200. Slide № 200
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func

    View Slide

  201. Slide № 201
    const enhance = connect(
    state => ({
    currentUser: selectCurrentUser(state),
    newNotificationCount: selectNewNotificationCount(state),
    newMessageCount: selectNewMessageCount(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout,
    onViewMessages: MessagesIO.viewMessages
    }
    )
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func

    View Slide

  202. Slide № 202
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore

    View Slide

  203. Slide № 203
    class Navigation extends Component {
    static propTypes = {
    currentUser: PropTypes.object,
    onLogin: PropTypes.func,
    onLogout: PropTypes.func,
    newNotificationCount: PropTypes.number,
    newMessageCount: PropTypes.number,
    onViewMessages: PropTypes.func
    }
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore

    View Slide

  204. Slide № 204
    class Navigation extends Component {
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    }
    {loggedIn &&

    View Slide

  205. Slide № 205
    class Navigation extends Component {
    render () {
    const loggedIn = !!this.props.currentUser
    return (



    My app



    Home
    Explore
    Search
    {loggedIn &&

    Notifications ({this.props.newNotificationCount})

    }
    {loggedIn &&

    View Slide

  206. Slide № 206
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search


    View Slide

  207. Slide № 207
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search







    )

    View Slide

  208. Slide № 208
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search







    )

    View Slide

  209. Slide № 209
    const LoginLogoutNavItem =

    View Slide

  210. Slide № 210
    const LoginLogoutNavItem = connect(
    )

    View Slide

  211. Slide № 211
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    )

    View Slide

  212. Slide № 212
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )

    View Slide

  213. Slide № 213
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  214. Slide № 214
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  215. Slide № 215
    User
    Navigation
    Navigation
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  216. Slide № 216
    User
    Navigation
    Navigation
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  217. Slide № 217
    User
    Navigation
    Navigation
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  218. Slide № 218
    User
    Navigation
    Navigation
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  219. Slide № 219
    User
    Navigation
    Navigation
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })
    Delegate rendering logic

    View Slide

  220. Slide № 220
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    Logout
    ) : (
    Login
    )
    )
    })

    View Slide

  221. Slide № 221
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })

    View Slide

  222. Slide № 222
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })

    View Slide

  223. Slide № 223
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })

    View Slide

  224. Slide № 224
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    LoginLogoutNavItem.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  225. Slide № 225
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    LoginLogoutNavItem.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  226. Slide № 226
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    LoginLogoutNavItem.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }
    User
    Only concerned about current user and authentication
    Not concerned about how to render it

    View Slide

  227. Slide № 227
    User
    const LoginLogoutNavItem = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function LoginLogoutNavItem (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    LoginLogoutNavItem.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  228. Slide № 228
    User
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  229. Slide № 229
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  230. Slide № 230
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }
    “Render props”

    View Slide

  231. Slide № 231
    Back to our

    View Slide

  232. Slide № 232
    class Navigation extends Component {
    render () {
    return (



    My app



    Home
    Explore
    Search







    )

    View Slide

  233. Slide № 233

    Home
    Explore
    Search







    )
    }
    }

    View Slide

  234. Slide № 234

    Home
    Explore
    Search







    )
    }
    }

    View Slide

  235. Slide № 235

    Home
    Explore
    Search




    />


    )
    }
    }
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  236. Slide № 236

    Home
    Explore
    Search




    renderLoggedOut={
    }
    renderLoggedIn={
    }
    />


    )
    }
    }
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  237. Slide № 237

    Home
    Explore
    Search




    renderLoggedOut={(login) =>
    Login
    }
    renderLoggedIn={(user, logout) =>
    Logout
    }
    />


    )
    }
    }
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  238. Slide № 238

    Home
    Explore
    Search




    renderLoggedOut={(login) =>
    Login
    }
    renderLoggedIn={(user, logout) =>
    Logout
    }
    />


    )
    }
    }
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  239. Slide № 239

    View Slide

  240. Slide № 240
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }

    View Slide

  241. Slide № 241
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }
    Objective: Connect UI to data source.

    View Slide

  242. Slide № 242
    const Auth = connect(
    state => ({
    currentUser: selectCurrentUser(state)
    }),
    {
    onLogin: LoginIO.showLoginModal,
    onLogout: LoginIO.logout
    }
    )(function Auth (props) {
    return (
    props.currentUser ? (
    props.renderLoggedIn(props.currentUser, props.onLogout)
    ) : (
    props.renderLoggedOut(props.onLogin)
    )
    )
    })
    Auth.propTypes = {
    renderLoggedIn: PropTypes.func.isRequired,
    renderLoggedOut: PropTypes.func.isRequired
    }
    “Data connector component”

    View Slide

  243. Slide № 243
    Navigation

    View Slide

  244. Slide № 244
    Navigation

    View Slide

  245. Slide № 245
    NavigationContainer
    NavigationView

    View Slide

  246. Slide № 246
    Navigation
    Auth
    Unread
    MessagesData
    Unread
    NotificationsData

    View Slide

  247. Slide № 247
    Navigation
    Auth
    Unread
    MessagesData
    Unread
    NotificationsData

    View Slide

  248. Slide № 248
    Conclusions

    View Slide

  249. Slide № 249
    When code reviewing
    Consistency
    Readability
    Logical correctness

    View Slide

  250. Slide № 250
    When code reviewing
    Consistency
    Readability
    Logical correctness
    Smells

    View Slide

  251. Slide № 251
    I don’t know of any way
    to detect these smells automatically.

    View Slide

  252. Slide № 252
    When writing/reviewing code,
    try looking for these smells.

    View Slide

  253. Slide № 253
    Not all smells are bad.

    View Slide

  254. Slide № 254
    Not all smells are bad.
    Example: Duplicated code

    View Slide

  255. Slide № 255
    Not all smells are bad.
    Example: Duplicated code
    can sometimes be easier to read

    View Slide

  256. Slide № 256

    View Slide

  257. Slide № 257
    Everything is a trade-off.

    View Slide

  258. Slide № 258
    Look for feedback.

    View Slide

  259. Slide № 259
    Thank you!

    View Slide