Skip to main content

React Snippets

Conditional HTML tag

An intrinsic element always begins with a lowercase letter, while a component always begins with an uppercase letter. The API is similar to Mantine's and MUI's button.

const Button = ({ component: Component = 'button', children, ...props }) => {
if (props.href) {
return <a {...props} />
}
return <Component {...props}>{children}</Component>
}

Icon

const ChevronRight = ({ ...props }) => {
return (
<svg width="24" height="24" {...props}>
<path d="M9 18l6-6-6-6" />
</svg>
)
}

// Alternative lighter syntax
const icon from './icon.svg';
function Icon(props) {
return <img src={icon} {...props} />
}

Debounce

// Pseudo code
let timeoutId = null
const debouncedFn = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => run(), 1000)
}

// Real React code
const timeoutRef = useRef(null)

useEffect(() => {
clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => getData(id), 1000)
}, [id])

Throttle

// Pseudo code
let shouldThrottle = false
const throttledFn = () => {
if (shouldThrottle) return
shouldThrottle = true
setTimeout(() => (shouldThrottle = false), 1000)

run()
}

// Real React code
// We don't use useState here because this value shouldn't trigger a re-render (NOT necessary for calculating the JSX)
const shouldThrottleRef = useRef(false)

useEffect(() => {
const getData = async (id) => {
if (shouldThrottleRef.current) return
shouldThrottleRef.current = true
setTimeout(() => (shouldThrottleRef.current = false), 1000)

run()
}

getData(id)
}, [id])

Lazy load with lazy & Suspense

import { lazy, Suspense } from 'react'

// this won't be included in the main bundle
const WeekPicker = lazy(() => import('./week-picker'))

const App = () => {
return (
<Suspense fallback={<Loading />}>
<WeekPicker />
</Suspense>
)
}

Flatten object (useful for nested state)

// From this...
const initialTravelPlan = {
id: 0,
title: '(Root)',
childPlaces: [
{
id: 1,
title: 'Earth',
childPlaces: [
{
id: 2,
title: 'Africa',
childPlaces: [
{
id: 3,
title: 'Botswana',
childPlaces: [],
},
],
},
],
},
],
}

// ...to this
const flatTravelPlan = {
0: {
id: 0,
title: '(Root)',
childIds: [1, 42, 46],
},
1: {
id: 1,
title: 'Earth',
childIds: [2, 10, 19, 26, 34],
},
}

Infinite scroll (Intersection Observer)

useEffect(() => {
const observer = new IntersectionObserver((entries) => {
// `entries` is an array of observed DOM nodes, in this case we only track one element (the last job card)
// "interseting" means the element is PARTIALLY visible in the viewport, which can be configured by `threshold` option
if (entries[0].isIntersecting) {
loadMore()
}
})

// Assume that the last element have a class of `job-card`
const lastElement = document.querySelector('.job-card:last-of-type')
if (lastElement) observer.observe(lastElement)

return () => {
if (lastElement) observer.unobserve(lastElement)
}
}, [])

Reduce re-rendering

Lift ur component up and pass it down as a prop

  1. ChildA gets compiled to React.createElement(ChildA, null) by Babel that creates a ReactElement of this shape { type: Child, props: {} }. props là ref value ChildA luôn bị re-render khi Parent re-render.
  2. ChildB & ChildC all passed as prop of Parent (children & lastChild respectively) no React.createElement is called for ChildB & ChildC because the references to ChildB & ChildC are the same between Parent's renders.
default function App() {
return (
<Parent lastChild={<ChildC />}>
<ChildB />
</Parent>
)
}

function Parent({ children, lastChild }) {
return (
<div className="parent">
<ChildA />
{children}
{lastChild}
</div>
)
}

function ChildA() {
return <div className="childA"></div>;
}

function ChildB() {
return <div className="childB"></div>;
}

function ChildC() {
return <div className="childC"></div>;
}

clsx + tailwind-merge helper

// Source: https://ui.shadcn.com/docs/installation/manual#add-a-cn-helper
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// https://frontendeval.com/questions/modal-overlay
import { useRef, useEffect, useState } from 'react'

function Modal({ isOpen, onClose, children }) {
const modalRef = useRef()

// Focus the modal when it mounts to make keyboard event work
useEffect(() => {
if (isOpen) {
modalRef.current.focus()
}
}, [isOpen])

// Prevent scrolling when modal is shown
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'auto'
}
}, [isOpen])

return isOpen ? (
// inset-0 applies top: 0, right: 0, bottom: 0, left: 0
// those will make a positioned element stretch to cover the whole viewport
// https://tailwindcss.com/docs/top-right-bottom-left
<div
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
ref={modalRef}
onClick={(e) => modalRef.current === e.target && onClose()} // Allow clicking outside to close the modal
tabIndex="0" // Make the `div` focusable
onKeyDown={(e) => e.code === 'Escape' && onClose()}
>
{children}
</div>
) : null
}

export default function ModalExample() {
const [showModal, setShowModal] = useState(false)

return (
<>
<button onClick={() => setShowModal(true)}>Open Modal</button>
<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
Hello
</Modal>
</>
)
}

Simulate expensive component

const ExpensiveComponent = () => {
const [percentage, setPercentage] = useState(0)

useEffect(() => {
// Simulate a loading process by increasing the percentage over time
const interval = setInterval(() => {
if (percentage < 100) {
setPercentage((prevPercentage) => prevPercentage + 1)
}
}, 100)

return () => {
clearInterval(interval)
}
}, [percentage])

return (
<div className="relative h-8 w-full bg-gray-300 rounded-full">
<div
className="absolute top-0 left-0 h-full bg-blue-500 rounded-full"
style={{ width: `${percentage}%` }}
/>
<div className="absolute inset-0 flex items-center justify-center text-gray-700 font-semibold">
{percentage}%
</div>
</div>
)
}

Different content on client and server

function MyComponent() {
const [didMount, setDidMount] = useState(false)

useEffect(() => {
// Effect does not run on the server
setDidMount(true)
}, [])

if (didMount) {
// ... return client-only JSX ...
} else {
// ... return initial JSX on the server ...
}
}

Module CSS

import s from '../Nav.module.scss'
return <h1 className={`${s['active-nav']} ${s.red}`}>Hello</h1>

Show JS obj ra page

<pre>{JSON.stringify(data, null, 2)}</pre>

window is not defined

// `useEffect` is also good as it only runs on browser (not server)
if (typeof document !== 'undefined') {
// running in a browser environment
} else {
// running in a server environment
}

Portal

Common use case: When the child components need to visually break out of the parent container.

import { createPortal } from 'react-dom'
const MyComponent = () => {
return (
<div>
{createPortal(
<h2>This h2 will be brought to #portal</h2>,
document.querySelector('#portal'), // nơi render trong index.html
)}
</div>
)
}

Force Re-render

const [, forceRerender] = useState()
forceRerender({})