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

React Component Anti-Patterns

React Component Anti-Patterns

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

June 03, 2021
Tweet

Transcript

  1. React Component Anti-Patterns Radoslav Stankov

  2. 👋

  3. Radoslav Stankov @rstankov

  4. None
  5. None
  6. https://rstankov.com/appearances

  7. Plan for today

  8. 1. History lesson 2. What is a good React component

    3. React anti-patterns Plan for today
  9. None
  10. 1 History lesson

  11. My history with React

  12. October 2014 Backbone early 2014 jQuery spaghetti February 2014 React

    on Rails May 2014 custom Flux December 2015 Redux
  13. React history

  14. 🍬 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
  15. 2 What is a good React component?

  16. 🍬 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?
  17. Is the component easy to understand?

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

  19. 1) import s 2) type s 3) constant s 4)

    default export of main componen t 5) subcomponents, hooks, functions
  20. // 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 <Placeholder /> ; } // 3.3) The happy path of the component return <div>My component</div> ; } // 4) Everything else, like helper functions, types, custom hooks, sub components
  21. ~/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
  22. 3 React anti-patterns

  23. What is Anti-Pattern? Anti-patterns are certain patterns in software development

    that are considered bad programming practices.
  24. 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.
  25. None
  26. None
  27. None
  28. 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
  29. 1. Nam e 2. Example s 3. What is the

    proble m 4. Alternatives
  30. Overwrite CSS of inner-component

  31. function Component({ className }) { return ( <div className={className}> <h1>something</h1>

    </div> ) ; } Overwrite CSS of inner component ⛔
  32. <Component className={styles.foo} /> Overwrite CSS of inner component ⛔

  33. .foo h1 { color: red ; } Overwrite CSS of

    inner component ✅
  34. Pass className to inner component

  35. <Component className={styles.foo} headerClassName={styles.bar} /> Pass className to inner component ⛔

  36. function Component({ className, headerClassName }) { return ( <div className={className}>

    <h1 className={headerClassName}>something</h1> </div> ) ; } Pass className to inner component ⛔
  37. <Component color="red" /> Pass className to inner component ✅

  38. <Component > <h1 className={styles.red}>whatever</h1> </Component> Pass className to inner component

  39. Overuse of renderless components

  40. <WindowResize onResize={onResize} /> <WindowScroll onScroll={onScroll} /> Overuse of renderless components

  41. useOnWindowResize(onResize ) useOnWindowScroll(onScroll) Overuse of renderless components ✅

  42. Class components with render methods

  43. class Component extends React.Component { render() { return <ul>{this.renderItems()}</ul> ;

    } renderItems() { return this.props.items.map(this.renderItem) ; } renderItem(item) { return <li>{item.title}</li> ; } } Class components with render methods ⛔
  44. function Component({ items }) { return ( <ul> {items.map((item) =>

    ( <Item key={item.id} item={item} /> ))} </ul> ) ; } function Item({ item }) { return <li>{item.title}</li> ; } Class components with render methods ✅
  45. Constant map instead of components

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

    , { to: '/about', title: 'About' } , { to: '/contact', title: 'Contact' } , // ... ] ; LIKES.map((link, i) => ( <MenuLink key={i} to={link.to} title={link.title} / > )); Constant map instead of components ⛔
  47. <MenuLink to="/" title="Home" /> <MenuLink to="/about" title="About" /> <MenuLink to="/contact"

    title="Contact" /> Constant map instead of components ✅
  48. Decompose record to props

  49. <Component {...post} /> Decompose record to props ⛔

  50. <Component postId={post.id} postTitle={post.title} postVoteCount={post.votesCount} /> Decompose record to props ⛔

  51. <Component post={post} /> Decompose record to props ✅

  52. 
 http://graphql.org/ 


  53. None
  54. apollo client:codegen

  55. 
 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 } `;
  56. // =================================================== = // GraphQL fragment: ProfileAvatarFragmen t // ===================================================

    = export interface ProfileAvatarFragment { __typename: "Profile" ; id: string ; name: string ; kind: string ; imageUrl: string | null ; } 
 graphql/types.ts 

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

  58. 
 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 } `;
  59. 
 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 } `;
  60. Query Fragment Fragment Fragment Fragment Fragment Fragment

  61. Screen Component Component Component Component Component Component

  62. <Component post={post} /> Decompose record to props ✅

  63. Wildcard typing

  64. Wildcard typing interface IProps { post: any; onClick: any; color:

    string; [key: string]: value; } ⛔
  65. Wildcard typing interface IProps extends React.HTMLAttributes<HTMLElement> { post: IPost; onClick:

    () => void; color: 'blue' | 'red'; } ✅
  66. Component as property

  67. <Text component="p" {...props} /> <Text component={Link} {...props} /> 
 <Text

    component={Flex} {...props} /> Component as property ⛔
  68. 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
  69. interface IProps { component: 'h1' | 'h2' | 'h3' |

    'p'; } Component as property ✅
  70. interface IProps extends React.HTMLAttributes<HTMLElement> { component: 'h1' | 'h2' |

    'h3' | 'p'; } Component as property ✅
  71. Unexpected defaults

  72. <Flex.Column responsive={false} {...props} /> Unexpected defaults ⛔

  73. 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 ✅
  74. Using two components instead of one

  75. <> {isActive && <Active />} {!isActive && <NotActive />} </>

    Using two components instead of one ⛔
  76. {isActive ? <Active /> : <NotActive />} Using two components

    instead of one ✅
  77. <>
 <Device.Phone> Only on phon e </Device.Phone> <Device.TableOrDesktop> On other

    device s </Device.TableOrDesktop> </> Using two components instead of one ⛔
  78. <Device.Phone> {(isPhone) => ( isPhone ? 'Only on phone' :

    'On other devices ' )} </Device.Phone> Using two components instead of one ✅
  79. Unneeded closures

  80. function Component({ clickHandle }) { return ( <button onClick={(e) =>

    clickHandle(e)} > clic k </button > ) ; } Unneeded closures ⛔
  81. function Component({ clickHandle }) { return ( <button onClick={clickHandle} >

    clic k </button > ) ; } Unneeded closures ✅
  82. Unneeded loops

  83. extractEdgeNode(connection).map((n) => ( <Node node={node} key={node.id} / > )); Unneeded

    loops ⛔
  84. mapEdgeNodes(connection, (n) => ( <Node node={node} key={node.id} /> ) );

    Unneeded loops ✅
  85. const nodes = extractEdgeNode(connection) ; if (!nodes.length) { return null

    ; } return nodes.map((n) => ( <Node node={node} key={node.id} /> ) ); Unneeded loops ⛔
  86. if (areEdgeNodesEmpty(connection)) { return null ; } return mapEdgeNodes(connection, (n)

    => ( <Node node={node} key={node.id} /> ) ); Unneeded loops ✅
  87. const nodes = extractEdgeNode(connection)[0]; Unneeded loops ⛔

  88. const nodes = first(connection); Unneeded loops ✅

  89. Recap

  90. 🚗 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
  91. None
  92. None
  93. Thanks 😎

  94. https://speakerdeck.com/rstankov