React Best Practices
I was working on React at my internship and had many questions about how to decide what is best practice to do something, which seemed subjective at that point to me. So I went through many resources online and constructed the following good practices guide.
Everything here is stated as an opinion and more of what works better and what does not.
Favor Functional Components
// 👎 Class components are verbose
class Counter extends React.Component {
state = {
counter: 0,
}
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ counter: this.state.counter + 1 })
}
render() {
return (
<div>
<p>counter: {this.state.counter}</p>
<button onClick ={this.handleClick}>Increment</button>
</div>
)
}
}
// 👍 Functional components are easier to read and maintain
function Counter() {
const [counter, setCounter] = useState(0)
handleClick = () => setCounter(counter + 1)
return (
<div>
<p>counter: {counter}</p>
<button onClick ={handleClick}>Increment</button>
</div>
)
}
Organize Helper Functions
Place helper functions into utils js file, so that they can be used across the React components. Helper functions shouldn't read from the component's state.
// 👎 Avoid nesting functions which don't need to hold a closure.
function Component({ date }) {
function parseDate(rawDate) {
...
}
return <div>Date is {parseDate(date)}</div>
}
// 👍 Place the helper functions before/outside the component
export function parseDate(date) { // utils.js
...
}
function Component({ date }) {
return <div>Date is {parseDate(date)}</div>
}
Don't Hardcode Markup
// 👎 Hardcoded markup is harder to manage.
function Filters({ onFilterClick }) {
return (
<>
<p>Book Genres</p>
<ul>
<li>
<div onClick ={() => onFilterClick('fiction')}>Fiction</div>
</li>
<li>
<div onClick ={() => onFilterClick('classics')}>
Classics
</div>
</li>
<li>
<div onClick ={() => onFilterClick('fantasy')}>Fantasy</div>
</li>
<li>
<div onClick ={() => onFilterClick('romance')}>Romance</div>
</li>
</ul>
</>
)
}
// 👍 Use loops and configuration objects
const GENRES = [
{
identifier: 'fiction',
name: Fiction,
},
{
identifier: 'classics',
name: Classics,
},
{
identifier: 'fantasy',
name: Fantasy,
},
{
identifier: 'romance',
name: Romance,
},
]
function Filters({ onFilterClick }) {
return (
<>
<p>Book Genres</p>
<ul>
{GENRES.map(genre => (
<li>
<div onClick ={() => onFilterClick(genre.identifier)}>
{genre.name}
</div>
</li>
))}
</ul>
</>
)
}
Destructure Props
// 👎 Don't repeat props everywhere in your component
function Input(props) {
return <input value={props.value} onChange ={props.onChange} />
}
// 👍 Destructure and use the values directly
function Component({ value, onChange }) {
const [state, setState] = useState('')
return <div>...</div>
}
Pass Objects Instead of Primitives
// 👎 Don't pass values on by one if they're related
<UserProfile
bio={user.bio}
name={user.name}
email={user.email}
subscription={user.subscription}
/>
// 👍 Use an object that holds all of them instead
<UserProfile user={user} />
Avoid Nested Ternary Operators
// 👎 Nested ternaries are hard to read in JSX
isSubscribed ? (
<ArticleRecommendations />
) : isRegistered ? (
<SubscribeCallToAction />
) : (
<RegisterCallToAction />
)
// 👍 Place them inside a component on their own
function CallToActionWidget({ subscribed, registered }) {
if (subscribed) {
return <ArticleRecommendations />
}
if (registered) {
return <SubscribeCallToAction />
}
return <RegisterCallToAction />
}
function Component() {
return (
<CallToActionWidget
subscribed={subscribed}
registered={registered}
/>
)
}
Move Lists in a Separate Component
// 👎 Don't write loops together with the rest of the markup
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
{articles.map(article => (
<div>
<h3>{article.title}</h3>
<p>{article.teaser}</p>
<img src={article.image} />
</div>
))}
<div>You are on page {page}</div>
<button onClick ={onNextPage}>Next</button>
</div>
)
}
// 👍 Extract the list in its own component
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
<ArticlesList articles={articles} />
<div>You are on page {page}</div>
<button onClick ={onNextPage}>Next</button>
</div>
)
}
Assign Default Props When Destructuring
// 👎 Don't define the default props outside of the function
function Component({ title, tags, subscribed }) {
return <div>...</div>
}
Component.defaultProps = {
title: '',
tags: [],
subscribed: false,
}
// 👍 Place them in the arguments list
function Component({ title = '', tags = [], subscribed = false }) {
return <div>...</div>
}
Use Reducers
// 👎 Don't use too many separate pieces of state
const TYPES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large'
}
function Component() {
const [isOpen, setIsOpen] = useState(false)
const [type, setType] = useState(TYPES.LARGE)
const [phone, setPhone] = useState('')
const [email, setEmail] = useState('')
const [error, setError] = useSatte(null)
return (
...
)
}
// 👍 Unify them in a reducer instead
const TYPES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large'
}
const initialState = {
isOpen: false,
type: TYPES.LARGE,
phone: '',
email: '',
error: null
}
const reducer = (state, action) => {
switch (action.type) {
...
default:
return state
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
...
)
}
Use Absolute Paths
// 👎 Don't use relative paths import Input from '../../../modules/common/components/Input' // 👍 Absolute ones don't change import Input from '@modules/common/components/Input'
Resources:
- https://alexkondov.com/tao-of-react/
- https://medium.com/@thejasonfile/dumb-components-and-smart-components-e7b33a698d43