Best Practices for Writing React Components

  • We use ES6 and ES7 syntax.
  • If you’re not sure of the distinction between presentational and container components, we recommend you read this first.
  • Please let us know in the comments if you have any suggestions, questions, or feedback.

Class Based Components

Importing CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

Initializing State

import React, { Component } from 'react'
import { observer } from 'mobx-react'import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'export default class ProfileContainer extends Component {
state = { expanded: false }

propTypes and defaultProps

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'export default class ProfileContainer extends Component {
state = { expanded: false }

static propTypes = {
model: object.isRequired,
title: string
}

static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}

Methods

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'export default class ProfileContainer extends Component {
state = { expanded: false }

static propTypes = {
model: object.isRequired,
title: string
}

static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
} handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}

handleNameChange = (e) => {
this.props.model.changeName(e.target.value)
}

handleExpand = (e) => {
e.preventDefault()
this.setState({ expanded: !this.state.expanded })
}

Passing setState a Function

this.setState({ expanded: !this.state.expanded })
this.setState(prevState => ({ expanded: !prevState.expanded }))

Destructuring Props

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'export default class ProfileContainer extends Component {
state = { expanded: false }

static propTypes = {
model: object.isRequired,
title: string
}

static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}

handleNameChange = (e) => {
this.props.model.changeName(e.target.value)
}

handleExpand = (e) => {
e.preventDefault()
this.setState(prevState => ({ expanded: !prevState.expanded }))
}

render() {
const {
model,
title
} = this.props
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}>
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
onChange={this.handleNameChange}
placeholder="Your Name"/>
</div>
</ExpandableForm>
)
}

}

Decorators

@observer
export default class ProfileContainer extends Component {
class ProfileContainer extends Component {
// Component code
}export default observer(ProfileContainer)

Closures

<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// ^ Not this. Use the below:
onChange={this.handleChange}
placeholder="Your Name"/>
import React, { Component } from "react";
import { observer } from "mobx-react";
import { string, object } from "prop-types";
// Separate local imports from dependencies
import ExpandableForm from "./ExpandableForm";
import "./styles/ProfileContainer.css";
// Use decorators if needed
@observer
export default class ProfileContainer extends Component {
state = { expanded: false };
// Initialize state here (ES7) or in a constructor method (ES6)
// Declare propTypes as static properties as early as possible
static propTypes = {
model: object.isRequired,
title: string,
};
// Default props below propTypes
static defaultProps = {
model: {
id: 0,
},
title: "Your Name",
};
// Use fat arrow functions for methods to preserve context (this will thus be the component instance)
handleSubmit = (e) => {
e.preventDefault();
this.props.model.save();
};
handleNameChange = (e) => {
this.props.model.name = e.target.value;
};
handleExpand = (e) => {
e.preventDefault();
this.setState((prevState) => ({ expanded: !prevState.expanded }));
};
render() {
// Destructure props for readability
const { model, title } = this.props;
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}
>
// Newline props if there are more than two
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// Avoid creating new closures in the render method- use methods like below
onChange={this.handleNameChange}
placeholder="Your Name"
/>
</div>
</ExpandableForm>
);
}
}

Functional Components

propTypes

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool
}// Component declaration

Destructuring Props and defaultProps

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}function ExpandableForm(props) {
const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={props.onSubmit}>
{props.children}
<button onClick={props.onExpand}>Expand</button>
</form>
)
}
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}
const ExpandableForm = ({ onExpand, expanded, children }) => {

Wrapping

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}
export default observer(ExpandableForm)
import React from "react";
import { observer } from "mobx-react";
import { func, bool } from "prop-types";
// Separate local imports from dependencies
import "./styles/Form.css";
// Declare propTypes here, before the component (taking advantage of JS function hoisting)
// You want these to be as visible as possible
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired,
};
// Destructure props like so, and use default arguments as a way of setting defaultProps
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? { height: "auto" } : { height: 0 };
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
);
}
// Wrap the component instead of decorating it
export default observer(ExpandableForm);

Conditionals in JSX

{
isTrue
? <p>True!</p>
: <none/>
}
{
isTrue &&
<p>True!</p>
}

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store