Avoid high mem ops in render functions
In React functional components, the body of your function is processed on EVERY render cycle. It is important to consider this and try to reduce processing power and memory usage whenever possible.
Consider these examples:
// β emojis does not use props or state, it does not need to live inside the render
// function. This creates an array ON EVERY RENDER
const MyStaticComponent = () => {
const emojis = Array.from({length: 5}, () => <div className="w-2 h-2">π</div>)
return (
<>{emojis}</>) }
// β
emojis is created once and then reused. This is the most ideal form as
// the calculation is done once ever, outside of the render loop (non-blocking)
const emojis = Array.from({length: 5}, () => <div className="w-2 h-2">π</div>)
const MyStaticComponent = () => (
{' '}
<>{emojis}</>)
// β This array gets freshly created on every render. This might be not be a problem
// if the component is rendered once on a page, but consider a situation where you
// render a 100 list of user reviews, each with their own rating. Now every page render
// has to recreate 100 arrays!
const MyRatings = ({ ratings }: { ratings: number[] }) => (
{' '}
<div className="some-css">
{ratings.map((todo) => (
<div key={`${rating}`}>{rating}</div>
))}
</div>
)
// β
Now each ratings component is only rendered once as long as the ratings
// do no change. Now the calculations are only done on the first page render.
const MyRatings = ({ ratings }: { ratings: number[] }) => {
const ratingsComponent = useMemo(
() => ratings.map(
todo => <div key={`${rating}`}>{ rating }</div>
)
, [ratings])
return (
<div className="some-css">{ratingsComponent}</div>) }
// β This button component creates a new onClick event function on EVERY RENDER
// #DeathByOneThousandCuts
const MyButton = ({ parentOnClick }: { parentOnClick: (e: MouseEvent) => void }) => (
<button onClick={(e) => {
// do something
// handle parent event
parentOnClick(e)
}
}>
My button
</div>
)
// β
Now the handler is only created once (unless the props change).
// This Component can now be rendered hundreds/thousands of times on a page and still be performant
const MyButton = ({ parentOnClick }: { parentOnClick: (e: MouseEvent) => void }) => {
const buttonHandler = useCallback((e: MouseEvent) => {
// do something
// handle parent event
parentOnClick(e)
},[parentOnClick])
return (
<button onClick={buttonHandler}>
My button
</div>
)
}
Prefer children over components as props
Use children only if it's a 1:1 child being rendered (add Container example)
Add official
Use named props
Prefer children over components as props
If a child is just being directly rendered in the βbodyβ of your component. Use children.
// β Needlessly verbose and can have weird edge cases
const WrapWithEmoji = ({ ComponentToWrap, emoji }: { ComponentToWrap: JSX.Element, emoji: string }) => (
{' '}
<>
{emoji}
{ComponentToWrap}
{emoji}
</>
)
// β
Uses native react types. Handles children in native react way.
const WrapWithEmoji = ({ children, emoji }: PropsWithChildren<{ emoji: string }>) => (
{' '}
<>
{emoji}
{children}
{emoji}
</>
)
The exception to this rule
When you require component props to be used in unconventional layouts where the child isnβt obviously the βbodyβ of the component. Or when you have multiple components being used at once in specific ways.
// β children is being used in a non-universal way. Very error prone. Not obvious
// from the parent where the children are going
const MyLayout = ({ children, emoji }: PropsWithChildren<{ emoji: string }>) => (
{' '}
<header>{children[0]}</header>
My Cool Layout! {emoji}
<div>{children[1]}</div>
<footer>{children[2]}</footer>)
// β
Props clearly define what the conponent is using the child for. Doesnt have
// all the ReactChildren edge cases
type MyLayoutProps = {
Header: JSX.Element,
Body: JSX.Element,
Footer: JSX.Element,
emoji: string
}
const MyLayout = ({ Header, Body, Footer, emoji }: MyLayoutProps) => (
{' '}
<header>{Header}</header>
My Cool Layout! {emoji}
<div>{Body}</div>
<footer>{Footer}</footer>)
Donβt use array index as key
Rendered arrays in React use keys in order to save time on render. When a prop array that is rendered changes, React uses the key to figure out where to add/remove rows so that it doesnβt have to rerender the entire array. If you use the index as the key, the entire array needs to rerender whenever there is a change. See official docs for more info.
// β List completely rerenders on every change to listItems
const MyList = ({ listItems } : { listItems: string[]) => (
{' '}
<>
listItems.map((item, index) => <div key={index}>{item}</div>)
</>
)
// β
Assuming listItem strings are unique this works.
const MyList = ({ listItems } : { listItems: string[]) => (
{' '}
<>
listItems.map((item) => <div key={item}>{item}</div>)
</>
)
// Or defer ordering to parent
// β
Let the parent decide what unique key to use
type ListItem = {
item: string
key: string
}
const MyList = ({ listItems } : { listItems: ListItem[]) => (
{' '}
<>
listItems.map(({(item, key)}) => <div key={key}>{item}</div>)
</>
)
Avoid complex/nested ternaries in render loop
Complex conditionals or nested ternary statements make it hard for people to reason about what is rendering. Instead, use the early return pattern (AKA guard statements). In cases where early returns do not work. Consider encapsulated ternaries in useMemoand then treating them like components.
// β Logic is hard to follow. Lots of things are kinda repeated
const MyPage = () => {
const { loading, data, error} = useSomeQuery()
const { loading: loading2, data: data2, error: error2} = useSomeOtherQuery()
return (
<>
{ loading && !error2 ? <Loading/> : loading2 ? <Loading/> : <MyHappyPathComponent data={{...data, ...data2}}/> : null }
{ error || error2 ? error ? <DisplayError error={error}/> : <DisplayError error={error2}/> : null}
</>
)
}
// β
Way easier to read.
const MyPage = () => {
const { loading, data, error} = useSomeQuery()
if(error) return <DisplayError error={error}/>
if(loading) return <Loading/>
// Please dont actually rename variables like this.Doing it for the demo...
const { loading: loading2, data: data2, error: error2} = useSomeOtherQuery()
if(error2) return <DisplayError error={error2}/>
if(loading2) return <Loading/>
return (<MyHappyPathComponent data={{...data, ...data2}}/>)
}
// β
Another way to do it when logic is coupled between lots of states.
// Ideally you want to extract SomeQueryComponent and SomeOtherQueryComponent
// into their own component function if possible. If you can't, this is a
// clean and performant way of rendering it.
const MyPage = () => {
const { loading, data, error} = useSomeQuery()
const SomeQueryComponent = useMemo(() => {
if(error) return <DisplayError error={error}/>
if(loading) return <Loading/>
return <SomeComponent data={data}/>
}, [error,loading, data])
const SomeOtherQueryComponent = useMemo(() => {
if(error2) return <DisplayError error={error}/>
if(loading2) return <Loading/>
return <SomeComponent data={data2}/>
}, [error2,loading2, data2])
return (
<article>
<p>Some text</p>
<h2>Something dependant on data</h2>
{SomeQueryComponent}
<h2>Something dependant on data2</h2>
{SomeOtherQueryComponent}
</article>
)
}
Keep HTML as flat as possible
As a general rule, the less HTML on the page, the faster it is to render and the easier it is to debug. React provides fragments in order to facilitate grouping HTML without needing to use an HTML element.
// β The div likely does nothing here. It carries no styling, and makes using this
// component in the parent harder to style
const MyList = ({ listItems } : { listItems: string[]) => (
<div>
listItems.map((item) => <div key={item}>{item}</div>)
<div/>
)
// β
the "`<>`" dissapears on render leaving you with clean HTML
const MyList = ({ listItems } : { listItems: string[]) => (
{' '}
<>
listItems.map((item) => <div key={item}>{item}</div>)
</>
)
Use ternaries instead of &&
Usually when something is missing we want to render null or undefined. But by not expliciting returning null using a ternary, we could have to deal with some unexpected behaviour because, for example, in JS 0 && "hey" returns 0
// β This will render: <ul>0</ul>
function ContactList() {
const contacts = []
return (
<ul>
{contacts.length &&
contacts.map((contact) => (
<li key={contact.id}>
{contact.name}
</li>
))}
</ul>
)
}
// β
This will render <ul></ul>
function ContactList() {
const contacts = []
return (
<ul>
{contacts.length ?
contacts.map((contact) => (
<li key={contact.id}>
{contact.name}
</li>
): null}
</ul>
)
}
This also happens with "" which is a very common scenario.
See https://kentcdodds.com/blog/use-ternaries-rather-than-and-and-in-jsx for a more in-depth explanation
/**
* TypeScript Utilities
*/
export type ValidValue<T> = Exclude<T, null | undefined | 0 | '' | false>
export type Optional<T> = T | undefined | null
/**
* Just like .filter(Boolean), but allows TypeScript to exclude falsy values in return type
* @example
* ```ts
* const x = [1, 2, 3, "", null, undefined, 0, false]
* const y = x.filter(BooleanFilter)
* // y is [1, 2, 3]
* ```
*/
export const BooleanFilter = <T,>(x: T): x is ValidValue<T> => Boolean(x)
export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
/**
* Strings
*/
export function capitaliseFirstChar(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function enumToNiceString(str: string) {
const newStr = str
.replace(/_/g, ' ')
.replace(/([A-Z])/g, ' $1')
.trim()
.toLowerCase()
return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
* Money
*/
export const formatMoneyCompact = (
currency: string | undefined,
amount: number,
) =>
new Intl.NumberFormat('en-GB', {
...(currency && {
style: 'currency',
currency: currency,
}),
notation: 'compact',
minimumFractionDigits: 0,
}).format(amount)
export const formatMoney = (currency: string | undefined, amount: number) => {
return new Intl.NumberFormat('en-GB', {
...(currency && {
style: 'currency',
currency: currency,
}),
minimumFractionDigits: 2,
currencyDisplay: 'narrowSymbol',
}).format(amount)
}
/**
* Cookies
*/
export const saveCookie = ({
key,
value,
maxAgeInSeconds = 30 * 24 * 60 * 60, // 30 days,
}: {
key: string
value: string | null
maxAgeInSeconds?: number
}): void => {
if (value === null) {
return
}
const domain = window?.location?.hostname?.split('.').slice(-2).join('.') // e.g. "blinkist.com"
document.cookie = `${key}=${value}; path=/; domain=.${domain}; max-age=${maxAgeInSeconds};`
}
export const getAllCookies = (): Record<string, string> | null => {
if (typeof window === 'undefined') {
return null
}
return document.cookie.split(';').reduce(
(acc, curr) =>
Object.assign(acc, {
[curr?.split('=')?.[0]?.trim() ?? '']: curr?.split('=')?.[1] ?? '',
}),
{},
)
}
export const getCookie = (key: string): string | null => {
return getAllCookies()?.[key] ?? null
}
/**
* Utils
*/
type Entries<T> = {
[K in keyof T]: [K, T[K]]
}[keyof T][]
export const getEntries = <T extends object>(obj: T) =>
Object.entries(obj) as Entries<T>
{
"key": "cmd+down",
"command": "cursorMove",
"when": "editorTextFocus",
"args": {
"to": "nextBlankLine",
"by": "wrappedLine"
}
},
{
"key": "cmd+up",
"command": "cursorMove",
"when": "editorTextFocus",
"args": {
"to": "prevBlankLine",
"by": "wrappedLine"
}
}
const t = Array(5)
// [ <5 empty items> ]
t.forEach((\_,i) => console.log(i))
// β nothing is printed
const t = Array.from({length: 5})
// [ undefined, undefined, undefined, undefined, undefined ]
t.forEach((\_,i) => console.log(i))
// β
0...4 is printed
// β
With initialization
const t = Array.from({length: 5}, (\_,i). => i+1)
// [1,2,3,4,5]
Links
Design Component Libraries
SEO
Typescript Goodies
type PropsWithChildren<P = unknown> = P & { children?: React.ReactNode }
PropsWithChildren
Landing page inspirations
My favorite blog posts
Startup
Typescript Goodies
- React type: PropsWithChildren
Tips
- Github -> .dev = editor
- https://x.com/_baretto/status/1828723629246341538
Python
AI tools
React type: PropsWithChildren
type PropsWithChildren<P> = P & { children?: ReactNode }
The best way to type icons
export const CheckedIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10.7388 16.0732L7.00793 12.3423L7.97487 11.3731L10.7388 14.1371L16.5336 8.3446L17.5005 9.31154L10.7388 16.0732Z"
fill="currentColor"
/>
</svg>
)
}