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

React Component Anti-Patterns

React Component Anti-Patterns

Radoslav Stankov

June 03, 2021
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. React Component
    Anti-Patterns
    Radoslav Stankov


    View full-size slide

  2. Radoslav Stankov
    @rstankov

    View full-size slide

  3. https://rstankov.com/appearances

    View full-size slide

  4. Plan for today

    View full-size slide

  5. 1. History lesson


    2. What is a good React component


    3. React anti-patterns
    Plan for today

    View full-size slide

  6. 1
    History lesson

    View full-size slide

  7. My history with React

    View full-size slide

  8. October 2014 Backbone
    early 2014 jQuery spaghetti
    February 2014 React on Rails
    May 2014 custom Flux
    December 2015 Redux

    View full-size slide

  9. React history

    View full-size slide

  10. 🍬 class component
    s

    🍫 css module
    s

    🍰 style component
    s

    🍪 functional component
    s

    🍩 high-order components

    🍥 redu
    x

    🍨 renderless component
    s

    🍧 children as functio
    n

    🍦 GraphQ
    L

    🎂 hooks

    View full-size slide

  11. 2
    What is a good React component?

    View full-size slide

  12. 🍬 What are the props this component needs
    ?

    🍫 What does this component do
    ?

    🍪 What are the component's states
    ?

    🍩 What is the component's logic
    ?

    🍨 How can we trace the logic
    ?

    🍧 Is it easy to refactor?

    View full-size slide

  13. Is the component easy to understand?

    View full-size slide

  14. ~/components/[Component]/index.js

    View full-size slide

  15. 1) import
    s

    2) type
    s

    3) constant
    s

    4) default export of main componen
    t

    5) subcomponents, hooks, functions

    View full-size slide

  16. // 1) imports
    import * as React from 'react'
    ;

    // ...
    // 2) component props (always named IProps)
    interface IProps {
    // ...
    }
    // 2) (optional) constants
    const SOME_VALUE = 1
    ;

    // ...
    // 3) default export of main component
    export default function ComponentName({
    prop1,
    prop2 = 'default value',
    }: IProps) {
    // 3.1) Hook
    s

    // 3.2) Guard clauses
    if (someCondition) {
    return null
    ;

    }
    if (someOtherCondiation) {
    return
    ;

    }
    // 3.3) The happy path of the component
    return My component
    ;

    }
    // 4) Everything else, like helper functions, types, custom hooks, sub components

    View full-size slide

  17. ~/components/[Component]/index.ts
    x

    ~/components/[Component]/Fragment.graphq
    l

    ~/components/[Component]/Mutation.graphq
    l

    ~/components/[Component]/styles.module.cs
    s

    ~/components/[Component]/[icon].sv
    g

    ~/components/[Component]/utils.t
    s

    ~/components/[Component]/utils.test.t
    s

    ~/components/[Component]/[PrivateSubComponent]/index.ts
    x

    ~/components/[Component]/[PrivateSubComponent]/...
    Component as folder

    View full-size slide

  18. 3
    React anti-patterns

    View full-size slide

  19. What is Anti-Pattern?
    Anti-patterns are certain patterns in software
    development that are considered bad
    programming practices.

    View full-size slide

  20. What is Anti-Pattern?
    As opposed to design patterns which are common
    approaches to common problems which have been
    formalized and are generally considered a good
    development practice, anti-patterns are the opposite
    and are undesirable.

    View full-size slide

  21. Reasons for anti-patterns
    • React and ecosystem evolved


    • JS -> Flow -> TS


    • We have inconsistent UI in designs


    • Workarounds


    • We learned among the way


    • Quick
    fi
    xes and path of least resistance

    View full-size slide

  22. 1. Nam
    e

    2. Example
    s

    3. What is the proble
    m

    4. Alternatives

    View full-size slide

  23. Overwrite CSS of inner-component

    View full-size slide

  24. function Component({ className }) {
    return
    (


    something

    )
    ;

    }
    Overwrite CSS of inner component

    View full-size slide


  25. Overwrite CSS of inner component

    View full-size slide

  26. .foo h1 {
    color: red
    ;

    }
    Overwrite CSS of inner component

    View full-size slide

  27. Pass className to inner component

    View full-size slide



  28. className={styles.foo}


    headerClassName={styles.bar} />
    Pass className to inner component

    View full-size slide

  29. function Component({ className, headerClassName }) {
    return
    (


    something

    )
    ;

    }
    Pass className to inner component

    View full-size slide


  30. Pass className to inner component

    View full-size slide

  31. >

    whatever

    Pass className to inner component

    View full-size slide

  32. Overuse of renderless components

    View full-size slide



  33. Overuse of renderless components

    View full-size slide

  34. useOnWindowResize(onResize
    )

    useOnWindowScroll(onScroll)
    Overuse of renderless components

    View full-size slide

  35. Class components with render methods

    View full-size slide

  36. class Component extends React.Component {
    render() {
    return {this.renderItems()}
    ;

    }
    renderItems() {
    return this.props.items.map(this.renderItem)
    ;

    }
    renderItem(item) {
    return {item.title}
    ;

    }
    }
    Class components with render methods

    View full-size slide

  37. function Component({ items }) {
    return
    (


    {items.map((item) =>
    (


    ))}

    )
    ;

    }
    function Item({ item }) {
    return {item.title}
    ;

    }
    Class components with render methods

    View full-size slide

  38. Constant map instead of components

    View full-size slide

  39. const LINKS = [
    { to: '/', title: 'Home' }
    ,

    { to: '/about', title: 'About' }
    ,

    { to: '/contact', title: 'Contact' }
    ,

    // ...
    ]
    ;

    LIKES.map((link, i) =>
    (

    >

    ));
    Constant map instead of components

    View full-size slide




  40. Constant map instead of components

    View full-size slide

  41. Decompose record to props

    View full-size slide


  42. Decompose record to props

    View full-size slide

  43. postId={post.id}
    postTitle={post.title}
    postVoteCount={post.votesCount}
    />
    Decompose record to props

    View full-size slide


  44. Decompose record to props

    View full-size slide


  45. http://graphql.org/

    View full-size slide

  46. apollo client:codegen

    View full-size slide


  47. components/Pro
    fi
    leAvatar/Fragment.ts

    import gql from 'graphql-tag'
    ;

    export default gql`
    fragment ProfileAvatarFragment on Profile
    {

    i
    d

    nam
    e

    kin
    d

    imageUr
    l

    }

    `;

    View full-size slide

  48. // ===================================================
    =

    // GraphQL fragment: ProfileAvatarFragmen
    t

    // ===================================================
    =

    export interface ProfileAvatarFragment
    {

    __typename: "Profile"
    ;

    id: string
    ;

    name: string
    ;

    kind: string
    ;

    imageUrl: string | null
    ;

    }

    graphql/types.ts

    View full-size slide

  49. import { ProfileAvatarFragment } from '~/types/graphql';

    View full-size slide


  50. components/Pro
    fi
    leCard/Fragment.ts

    import gql from 'graphql-tag'
    ;

    import ProfileAvatarFragment from '~/components/ProfileAvatar/Fragment'
    ;

    export default gql`
    fragment ProfileCardFragment on Profile
    {

    i
    d

    nam
    e

    slu
    g

    ...ProfileAvatarFragmen
    t

    }

    ${ProfileAvatarFragment
    }

    `;

    View full-size slide


  51. screens/Pro
    fi
    le/Query.ts

    import gql from 'graphql-tag'
    ;

    import ProfileCardFragment from '~/components/ProfileCard/Fragment';

    // ... more fragment
    s

    export default gql`
    query ProfileScreen($id: ID!)
    {

    profile(id: $id)
    {

    i
    d

    ...ProfileCardFragmen
    t

    // ... more fragment
    s

    }

    }

    ${ProfileCardFragment
    }

    `;

    View full-size slide

  52. Query
    Fragment Fragment
    Fragment
    Fragment
    Fragment
    Fragment

    View full-size slide

  53. Screen
    Component Component
    Component
    Component
    Component
    Component

    View full-size slide


  54. Decompose record to props

    View full-size slide

  55. Wildcard typing

    View full-size slide

  56. Wildcard typing
    interface IProps {
    post: any;
    onClick: any;
    color: string;
    [key: string]: value;
    }

    View full-size slide

  57. Wildcard typing
    interface IProps extends React.HTMLAttributes {
    post: IPost;
    onClick: () => void;
    color: 'blue' | 'red';
    }

    View full-size slide

  58. Component as property

    View full-size slide




  59. Component as property

    View full-size slide

  60. interface IProps {
    // ...
    /** Props passed down to the custom `component` */
    dangerouslySetInnerHTML?: { __html: string | null };
    href?: string;
    id?: string;
    itemMargin?: string;
    justify?: string;
    meta?: any;
    onClick?: any;
    responsive?: false;
    subjectId?: string;
    subjectType?: string;
    target?: string;
    title?: string | null;
    to?: string;
    rel?: string;
    style?: any;
    width?: string;
    colSpan?: number;
    Component as property

    View full-size slide

  61. interface IProps {
    component: 'h1' | 'h2' | 'h3' | 'p';
    }
    Component as property

    View full-size slide

  62. interface IProps extends React.HTMLAttributes {
    component: 'h1' | 'h2' | 'h3' | 'p';
    }
    Component as property

    View full-size slide

  63. Unexpected defaults

    View full-size slide


  64. Unexpected defaults

    View full-size slide

  65. 1. Be careful when pick defaults


    2. Default is false/inactive by default


    3. Have couple of cases when default is turn off


    4. When turn off default, check if most uses are with it


    5. Don't be afraid to change a default


    6. Don't be afraid to add default later, when see uses
    Unexpected defaults

    View full-size slide

  66. Using two components instead of one

    View full-size slide

  67. <>
    {isActive && }
    {!isActive && }
    >
    Using two components instead of one

    View full-size slide

  68. {isActive ? : }
    Using two components instead of one

    View full-size slide

  69. <>


    Only on phon
    e



    On other device
    s


    >
    Using two components instead of one

    View full-size slide


  70. {(isPhone) =>
    (

    isPhone ? 'Only on phone' : 'On other devices
    '

    )}

    Using two components instead of one

    View full-size slide

  71. Unneeded closures

    View full-size slide

  72. function Component({ clickHandle }) {
    return
    (

    clickHandle(e)}
    >

    clic
    k

    >

    )
    ;

    }
    Unneeded closures

    View full-size slide

  73. function Component({ clickHandle }) {
    return
    (

    >

    clic
    k

    >

    )
    ;

    }
    Unneeded closures

    View full-size slide

  74. Unneeded loops

    View full-size slide

  75. extractEdgeNode(connection).map((n) =>
    (

    >

    ));
    Unneeded loops

    View full-size slide

  76. mapEdgeNodes(connection, (n) =>
    (


    )

    );
    Unneeded loops

    View full-size slide

  77. const nodes = extractEdgeNode(connection)
    ;

    if (!nodes.length) {
    return null
    ;

    }
    return nodes.map((n) =>
    (


    )

    );
    Unneeded loops

    View full-size slide

  78. if (areEdgeNodesEmpty(connection)) {
    return null
    ;

    }
    return mapEdgeNodes(connection, (n) =>
    (


    )

    );
    Unneeded loops

    View full-size slide

  79. const nodes = extractEdgeNode(connection)[0];
    Unneeded loops

    View full-size slide

  80. const nodes = first(connection);
    Unneeded loops

    View full-size slide

  81. 🚗 Overwrite CSS of inner-component


    🚙 Pass className to inner component


    🏎 Overuse of renderless components


    🚕 Class components with render methods


    🚓 Constant map instead of components


    🚙 Decompose record to props


    🚚 Wildcard typing


    🚐 Component as property


    🚎 Unexpected defaults


    🚐 Using two components instead of one

    🚛 Unneeded closures


    🚌 Unneeded loops


    🛻 Variant property

    View full-size slide

  82. https://speakerdeck.com/rstankov

    View full-size slide