π§ π§ π§ This document is a work in progress π§ π§ π§
- Introduction
- The Bare Minimum
- Design for happiness
- Performance tips
- Testing principles
Note:
As I was writing this, I realized that it was actually difficult for me to separate my thoughts into the design
, performance
, and testing
. I noticed that a lot of designs aimed for maintainability also makes your application faster. How cool is that?
I noticed that most of the techniques are variations of basic refactoring methods, SOLID principles, and programming ideas, just applied to React specifically.
react-philosophies
is:
- things I think about before I write
React
code. - at the back of my mind whenever I review someone else's code or my own
- just guidelines and NOT rigid rules
- a living document and will evolve overtime as I my experience grows
A lot of these things may feel like very basic and common sense but I've worked with many large complex applications that seem to... well, not care.
react-philosophies
is inspired by various places I've stumbled upon at different points of my coding journey.
Most notably:
- Tim Peters: Zen of Python (Pep 20)
- Dev Cheney: Zen of Go
- Sandi Metz
- Martin Fowler
- Kent C Dodds
- Dan Abramov
- Bob Martin (Not saying I agree with his political views)
- wiki.c2.com
- sapegin/washingcode-book
- trekhleb/state-of-the-art-shitcode
- droogans/unmaintainable-code
If there's something that you think should be part of my reading list, or if you have great ideas that you think I should include here, don't hesitate to submit a PR or an issue; I'll check it out.
- Use ESLint including
rule-of-hooks
andexhaustive-deps
- Do NOT ignore exhaustive-deps warnings / errors for
useMemo
,useCallback
anduseEffect
- Remember to add keys whenever you use
map
to display components - Do NOT ignore the warning "Can't perform state update on unmounted component."
- If you see a warning or error in the console, Do not ignore it.
- Use Prettier to automatically format your code
- Use Code Climate (or similar) to detect code smells
The best code is no code at all Every new line of code you willingly bring into the world is code that has to be debugged, code that has to be read and understood, code that has to be supported." - Jeff Atwood
"One of my most productive days was throwing away 1000 lines of code." - Eric S. Raymond
"If I Had More Time, I Would Have Written a Shorter Letter" - Attributed to Blaise Pascal, Mark Twain, among others..
See also:
TLDR:
- Think first before adding another dependency (loadash, fetching libraries, redux)
- Use
map
,filter
if you can - Simplify complex conditionals
- π You're probably NOT gonna need it.
- Avoid premature generalization
- Think about the expensive setup
βAny fool can write code that a computer can understand. Good programmers write code that humans can understand.β - Martin Fowler βIndeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. So if you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to readβ β Robert C. Martin
TLDR:
- Derive states to avoid state management complexity
- Pass the banana, not the gorilla and the entire jungle
- Separate concerns with the single responsibility principle
logical
andpresentational
components- Duplication is far cheaper than the wrong abstraction
- Avoid prop drilling by using composition
- Prefer passing primitives as props
- Breakdown
useEffect
s - Extract logic to hooks and helper functions
- Prefer having mostly primitives as dependencies to
useCallback
,useMemo
anduseEffect
- Do not put too many dependencies in
useCallback
,useMemo
anduseEffect
Context
is not the solution for every state sharing problem
Suppose you fetch a list of two numbers {a: number, b: number}[]
. The two numbers represent the two shorter sides
of a right triangle. The length of the three sides, the perimeter, and the area of each triangle should be displayed
β Bad Solution
const TriangleInfo = () => {
const [triangleInfo, setTriangleInfo] = useTriangles<{a: number, b: number}>([])
const [hypotenuses, setHypotenuses] = useState<number[]>([])
const [perimeters, setPerimeters] = useState<number[]>([])
const [areas, setAreas] = useState<number[]>([])
useEffect(() => {
fetchTriangles().then(r => {
setTriangleInfo(r)
setHypotenuses(r.map(t => computeHypotenuse(t.a, t.b))
setArea(r.map(t => computeArea(t.a, t.b))
})
}, [])
useEffect(() => {
setHypotenuses(triangleInfo.map(t => computeHypotenuse(t.a, t.b))
setArea(triangleInfo.map(t => computeArea(t.a, t.b))
}, [triangleInfo])
useEffect(() => {
const p = triangleInfo((t, i) => {
return computePerimeter(t.a, t.b, hypotenuse[i])
})
}, [triangleInfo, hypotenuses])
/*** show here info here ****/
}
β Better Solution
const TriangleInfo = () => {
const [triangleInfo, setTriangleInfo] = useTriangles<{a: number, b: number}>([])
useEffect(() => {
fetchTriangles().then(r => setTriangleInfo(r))
}, [])
const areas = triangleInfo.map(t => computeArea(t.a, t.b))
const hypotenuses = triangleInfo.map(t => computeHypotenuse(t.a, t.b))
const perimeters = triangleInfo.map((t, i) => computePerimeters(t.a, t.b, hypotenuses[i]))
/*** show here info here ****/
}
Suppose you are assigned to make a component which fetches a list of unique points
There is a button to either sort by x or y and a button
to filter points that farther than a specific distance from the origin (0, 0)
β Bad Solution
type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x'
const Points = () => {
const [points, setPoints] = useState<{x: number, y: number}[]>([])
const [filteredPoints, setFilteredPoints] = useState<{x: number, y: number}[]>([])
const [sortedPoints, setSortedPoints] = useState<{x: number, y: number}[]>([])
const [maxDistance, setMaxDistance] = useState<number>(Infinity)
const [sortBy, setSortBy] = useState<SortBy>('x')
useEffect(() => {
fetchPoints().then(r => setPoints(r))
}, [])
useEffect(() => {
setSortedPoints(sortPoints(points, sortBy))
}, [sortBy, points])
useEffect(() => {
setFilteredPoints(sortedPoints.filter(p => getDistance(p.x, p.y) < maxDistance))
}, [sortedPoints, maxDistance])
const otherSortBy = toggle(sortBy)
return (
<>
<button onClick={() => { setSortBy(otherSortBy)}}>Sort by: {otherSortBy} <button>
<button onClick={() => { setMaxDistance(maxDistance + 5)}}>Add 5 to max distance<button>
Showing only points that are less than {maxDistance} units away from origin `(0, 0)`
<ol>{filteredPoints.map(p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li>}
</>
)
}
β Better Solution
type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x'
const Points = () => {
const [points, setPoints] = useState<{x: number, y: number}[]>([])
const [maxDistance, setMaxDistance] = useState<number>(Infinity)
const [sortBy, setSortBy] = useState<SortBy>('x')
useEffect(() => {
fetchPoints().then(r => setPoints(r))
}, [])
const otherSortBy = toggle(sortBy)
const filteredPoints = points.filter(p => getDistance(p.x, p.y) < maxDistance)
return (
<>
<button onClick={() => { setSortBy(otherSortBy)}}>Sort by: {otherSortBy} <button>
<button onClick={() => { setMaxDistance(maxDistance + 5)}}>Add 5 to max distance<button>
Showing only points that are less than {maxDistance} units away from origin `(0, 0)`
<ol>{sortPoints(filteredPoints, sortBy).map(p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li>}
</>
)
}
β β bad β β
const decrement = useCallback(() => setCount(count - 1), [count])
β
β
Better β
β
const decrement = useCallback(() => setCount(count => count - 1), [])
Premature optimization is the root of all evil
TLDR:
- Measure first with the React profiler
- Splitting code to bundles
useMemo
for expensive calculationsReact.memo
for reducing re-renders- Make sure your
useCallback
anduseMemo
is doing what you think it's doing (preventing rerendering) - Window large lists (with React virtual or similar)
- Put
Context
as low as possible in your component tree.Context
does not have to be global to your whole app. Context
should be logically separated- You can optimize
context
by separating thestate
anddispatch
function - Stop punching yourself everytime you blink (fixing slow rerenders before fixing rerenders)
TLDR:
- Your tests should resemble the way your software is used
- Stop testing implementation details
- If your tests don't make you confident that you didn't break anything, then it didn't do its (on and only) job
- You don't need 100% code coverage, about 70% is okay