marocchino / react-patterns

How we write React

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React

Mostly reasonable patterns for writing React in CoffeeScript.

Table of Contents

  1. Caveat
  2. Organization
  3. Component Organization
  4. Formatting Props
  5. Patterns
  6. Computed Props
  7. Compound State
  8. Sub-render
  9. Transclusion and Layouts
  10. Anti-patterns
  11. Compound Conditions
  12. Cached State in render
  13. Existence Checking
  14. Setting State from Props
  15. Practices
  16. Naming Handle Methods
  17. Naming Events
  18. Using PropTypes
  19. Using Entities
  20. Gotchas
  21. Tables
  22. Add-ons
  23. ClassSet
  24. JSX

Caveat

These patterns and practices are birthed from our experience writing React on Rails.

We weight the trade-off of bloating components with get, is and sub-render methods. While they clutter a component's public interface, they are a huge maintainability win.

⬆ back to top


Component Organization

Group methods into logical groups.

  • mixins
  • propTypes
  • get methods
  • state methods
  • lifecycle events
  • render
  • event handlers
  • "private" methods
Person = React.createClass
  mixins: [MammalMixin]

  propTypes:
    name: React.PropTypes.string

  getInitialState: ->
    smiling: false

  getDefaultProps: ->
    name: ''

  componentWillMount: ->   # add event listeners (Flux Store, WebSocket, document)

  componentDidMount: ->    # data request (XHR)

  componentWillUnmount: -> # remove event listeners

  render: ->
      React.DOM.div
        className: 'Person'
        onClick:   @handleClick,
          @props.name
          " is smiling" if @state.smiling

   handleClick: ->
     @setState(smiling: !@state.smiling)

  # private

  _doSomethingUglyOrOutsideMyConcern: ->
    # Concerns with outside objects,
    # dirty or duplicated implementations,
    # etc.

Place get methods (computed props) after React's getInitialState and getDefaultProps.

Place has/is/can methods (compound state) after that, respectively.

Person = React.createClass
  getInitialState: ->

  getDefaultProps: ->

  getFormattedBirthDate: ->

  hasHighExpectations: ->

  isLikelyToBeDissapointedWithSurprisePartyEfforts: ->

⬆ back to top

Formatting Props

Wrap props on newlines for exactly 2 or more.

(Hint: Don't separate props with commas)

# ok
Person({firstName: "Michael"})

# bad
Person({firstName: "Michael", lastName: "Chan", occupation: "Web Developer", favoriteFood: "Drunken Noodles"})

# good
Person
  firstName:    "Michael"
  lastName:     "Chan"
  occupation:   "Web Developer"
  favoriteFood: "Drunken Noodles"
  onChange:     @handleChange

⬆ back to top


Computed Props

Name computed prop methods with the get prefix.

# bad
firstAndLastName: ->
  "#{@props.firstName} #{@props.lastname}"

# good
getFullName: ->
  "#{@props.firstName} #{@props.lastname}"

See: Cached State in render anti-pattern

⬆ back to top


Compound State

Name compound state methods with the is, has or can prefix.

# bad
happyAndKnowsIt: ->
  @state.happy and @state.knowsIt

# good
isWillingSongParticipant: ->
  @state.happy and @state.knowsIt

hasWorrysomeBehavior: ->
  !@isWillingSongParticipant() and @props.punchesKittens

canPetKittens: ->
  @hasHands() and @props.kittens.length

These methods should return a boolean value.

See: Compound Conditions anti-pattern

⬆ back to top

Sub-render

Use sub-render methods to isolate logical chunks of component UI.

# good
render: ->
  createItem = (itemText) ->
    React.DOM.li(null, itemText)

  React.DOM.ul(null, @props.items.map(createItem))

# better
render: ->
  React.DOM.ul(null, @renderItems())

renderItems: ->
  for itemText in @props.items
    React.DOM.li(null, itemText)

⬆ back to top

Transclusion and Layouts

Use transclusion (i.e., passing children through the component) to wrap components in layout. Don't create one-off components that merge layout and domain components.

# bad
PeopleWrappedInBSRow = React.createClass
  render: ->
    React.DOM.div className: 'row',
      People people: @state.people

# good
BSRow = React.createClass
  render: ->
    React.DOM.div
      className: 'row'
        @props.children

SomeHigherView = React.createClass
  render: ->
    React.DOM.div null,
      BSRow null,
        People(people: @state.people),

This works nicely for complex components—like Tabs or Tables—where you you might need to iterate over children and place them within a complex layout.

⬆ back to top


Cached State in render

Do not keep state in render

# bad
render: ->
  name = 'Mr. ' + @props.name
  React.DOM.div(null, name)

# good
render: ->
  React.DOM.div(null, 'Mr. ' + @props.name)

# good (complex example)
getFormattedBirthDate: ->
  moment(@props.user.bday).format(LL);

render: ->
  React.DOM.div(null, @getFormattedBirthDate())

See: Computed Props pattern

⬆ back to top

Compound Conditions

Do not put compound conditions in render.

#bad
render: ->
  if @state.happy and @state.knowsIt
    React.DOM.div(null, "Knows what it's all about.")

#good
isLikeTotallyHappy: ->
  @state.happy and @state.knowsIt

render: ->
  if @isLikeTotallyHappy()
    React.DOM.div(null, "Knows what it's all about.")

See: Compound State pattern

⬆ back to top

Existence Checking

Do not check existence of prop objects.

#bad
render: ->
  if @props.person?.firstName
    React.DOM.div(null, @props.person.firstName)
  else
    null

#good
getDefaultProps: ->
  person:
    firstName: ''

render: ->
  React.DOM.div(null, @props.person.firstName)

⬆ back to top

Setting State from Props

Do not set state from props without obvious intent.

#bad
getInitialState: ->
  items: @props.items

#good
getInitialState: ->
  items: @props.initialItems

Read: "Props is getInitialState Is an Anti-Pattern"

⬆ back to top


Naming Handler Methods

Name the handler methods after their triggering event.

# bad
render: ->
  React.DOM.div
    onClick: @punchABadger

punchABadger: ->

# good
render: ->
  React.DOM.div
    onClick: @handleClick

handleClick: ->

Handler names should:

  • begin with handle
  • end with the name of the event they handle (eg, Click, Change)
  • be present-tense

If you need to disambiguate handlers, add additional information between handle and the event name. For example, you can distinguish between onChange handlers: handleNameChange and handleAgeChange. If you do this, check whether you should actually create another component class.

⬆ back to top

Naming Events

Use custom event names for components Parent-Child event listeners.

Parent = React.createClass
  render: ->
    React.DOM.div
      className: 'Parent'
        Child(onCry: handleCry) # custom event `cry`

  handleCry: ->
    # handle childs' cry

Child = React.createClass
  render: ->
    React.DOM.div
      className: 'Child'
      onChange:  @props.onCry # React DOM event

⬆ back to top

Using PropTypes

Use PropTypes to communicate expectations and log meaningful warnings.

MyValidatedComponent = React.createClass
  propTypes:
    name: React.PropTypes.string

This component will log a warning if it receives name of a type other than string.

Person({name: 1337})
# Warning: Invalid prop `name` of type `number` supplied to `MyValidatedComponent`, expected `string`.

Components may require props

MyValidatedComponent = React.createClass
  propTypes:
    name: React.PropTypes.string.isRequired

This component will now validate the presence of name.

Person()
# Warning: Required prop `name` was not specified in `Person`

Read: Prop Validation

⬆ back to top

Using Entities

Use Reacts String.fromCharCode() for special characters.

# bad
React.DOM.div(null, 'PiCO · Mascot')

# nope
React.DOM.div(null, 'PiCO · Mascot')

# good
React.DOM.div(null, 'PiCO ' + String.fromCharCode(183) + ' Mascot')

Read: JSX Gotchas

⬆ back to top

Tables

The browser thinks you're dumb. But React doesn't. Always use tbody in your table components.

# bad
render: ->
  React.DOM.table null,
    React.DOM.tr null, ''

# good
render: ->
  React.DOM.table null,
    React.DOM.tbody null,
      React.DOM.tr null, ''

The browser is going to insert tbody if you forget. React will continue to insert new trs into the table and confuse the heck out of you. Always use tbody.

⬆ back to top

classSet

Use the classSet() add-on to manage conditional classes in your app:

# bad
render: ->
  React.DOM.div
    className: @getClassName()

getClassName: ->
  claasses = ['MyComponent']
  classes.push('MyComponent--active') if @state.active
  classes.join(' ')

# good
render: ->
  classes =
    'MyComponent': true
    'MyComponent--active': @state.active

  React.DOM.div
    className: React.addons.classSet(classes)

Read: Class Name Manipulation

⬆ back to top

JSX

Don't use JSX or CJSX in CoffeeScript.

# bad
render: ->
  `(
    <div
     className: "noJSX"
     orClick:   {@handleClick}>
      Save the children.
    </div>
   )`

#good
render: ->
  React.DOM.div
    className: "noJSX"
    onClick:   @handleClick,
      'Save the children.'

Read: CoffeeScript and JSX for more on our decision to avoid JSX.

⬆ back to top

About

How we write React

License:MIT License