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
- Gotchas
- Tables
- Add-ons
- ClassSet
- 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 component's public interface, they are a huge maintainability win.
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: ->
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
, 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
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 (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.
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 @props.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: ->
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.
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
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 tr
s into the table
and confuse the heck out of you. Always use
tbody
.
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
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.