Mostly reasonable patterns for writing React in CoffeeScript.
- Caveat
- Organization
- Component Organization
- Formatting Props
- Patterns
- Computed Props
- Compound State
- Sub-render
- Transclusion and Layouts
- Anti-patterns
- Compound Conditions
- Cached State in render
- Existence Checking
- Setting State from Props
- Practices
- Naming Handle Methods
- Naming Events
- Using PropTypes
- Using Entities
- JSX
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 components public interfaces, they are a huge maintainability win.
Group methods into logical groups.
- mixins
- propTypes
- getters
- lifecycle events
- render
- event handlers
- "private" methods
Person = React.createClass
mixins: [MammalMixin]
propTypes:
name: React.PropTypes.string
getDefaultProps: ->
name: ''
getInitialState: ->
smiling: false
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
_doSomethingInOutsideMyself: ->
# Concerns with outside objects,
# dirty or duplicated implementations,
# etc.
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
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
Name compound state methods with the is
or has
prefix:
# bad
happyAndKnowsIt: ->
@state.happy and @state.knowsIt
# good
isWillingSongParticipant: ->
@state.happy and @state.knowsIt
hasWorrysomeBehavior: ->
!@isWillingSongParticipant() and @props.punchesKittens
See: Compound Conditions anti-pattern
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)
Use transclusion 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.
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
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
Do not check existence of prop
objects.
#bad
render: ->
if person?.firstName
React.DOM.div(null, @props.person.firstName)
else
null
#good
getDefaultProps: ->
person:
firstName: ''
render: ->
React.DOM.div(null, @props.person.firstName)
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"
Name the handler methods after their triggering event.
# bad
render: ->
React.DOM.div
onClick: @punchABadger
punchABadger: ->
# good
render: ->
React.DOM.div
onClick: @handleClick
handleClick: ->
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
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
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
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.