React
- Building User Interface
- Single-page Application
- Create Reuseable UI Components (Virtual DOM)
- To use React in production, you need npm which is included with Node.js.
Creating a React App
Using a build tool
- Vite has a rich ecosystem of plugins to support fast refresh, JSX, Babel/SWC, and other common features
- Parcel supports fast refresh, JSX, TypeScript, Flow, and styling out of the box.
- Rsbuild includes built-in support for React features like fast refresh, JSX, TypeScript, and styling.
npm create vite@latest app-name --template react
# Using TypeScript
npm create vite@latest app-name --template react-ts
# Parcel
npm install --save-dev parcel
# Rsbuild
npx create-rsbuild --template react
Using Full-stack Frameworks
- Next.js takes full advantage of React’s architecture to enable full-stack React apps.
- React Router emphasizes standard Web APIs and has several ready to deploy templates for various JavaScript runtimes and platforms.
- Remix is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.
- Tanstack Start provides a full-document SSR, streaming, server functions, bundling, and more using tools like Nitro and Vite.
- RedwoodJS is a full stack React framework with lots of pre-installed packages and configuration that makes it easy to build full-stack web applications.
# Next
npx create-next-app@latest
# React Router
npx create-react-router@latest
Render
<body>
<!-- Your code will put inside root (or any id) div -->
<div id="root"></div>
</body>
import ReactDOM from "react-dom/client";
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<p>Hello</p>);
JavaScript XML (JSX)
// `<>` | `<Fragment>` | `<React.Fragment>`
const secretText = "Daniel";
const age = 17;
const users = (
<>
<div>John Doe</div>
<div className="woman">Mary Jane</div>
<input type="text" />
<div>
{1 + 1} {secretText}
</div> // Expression {}
{age >= 18 ? "mature" : "child"} // if statement
</>
);
Components
Class Component
| Function | Feature |
|---|---|
contructor(props?) { super(props?); // state configs } | |
static getDerivedStateFormProps(props, state) { return {} } | Call before render (create & update) |
render() { return <JSX/> } | |
componentDidMount() {} | Call after 1st render |
shouldComponentUpdate() { return boolean; } | Set up 1 time |
getSnapshotBeforeUpdate(props, state) {} | access props & state before update |
componentDidUpdate() {} | Call after component update |
componentWillUnmount() {} | Call before component is about to be removed |
class Car1 extends React.Component {
constructor(props) {
super(props);
this.state = {
color: "red",
};
}
changeColor = () => this.setState({ color: "blue" });
render() {
return (
<h2>
I am a {this.state.color} {this.props.model}!
</h2>
);
}
}
// Use with props: <Car1 model="Mustang" />
Function Component
// props = { children: null|JSX.Element, attr1: value1, attr2: value2, ..., attrN: valueN }`
function Name(props?) {
return <JSX />;
}
// Usage
<Component attr1={value1} {...otherAttr} />
<Parent {...attrs}><Children /></Parent>
// Event based on HTML Events
// on<EventName>={(event) => {}}
<button type="button" onClick={(e) => {}}>
Click
</button>
// conditions ? <CompIfTrue /> : <CompIfFalse />
function HomePage(user) {
return <div>{user ? <Home /> : <Login />}</div>;
}
function Calendar({ isAllowedButtonControls }) {
return (
<>
{/* Other components */}
{isAllowedButtonControls && <ButtonControls />}
</>
);
}
function ItemList({ items }) {
return (
<>
{/* Each children should have a unique key */}
{items.map((item, index) => {
return <JSX key={index} {...item} />;
})}
</>
);
}
function Car2() {
return <h2>Hi, I am a Car!</h2>;
}
// Arrow function (with default props)
const Garage = ({ showGarage = true }) => {
if (!showGarage) return <Fragment />;
return (
<>
<h1>Who lives in my Garage?</h1>
<Car2 /> {/* Component nested */}
</>
);
};
export default Garage;
// split component to reuse
// Using: import Garage from 'components/Garage';
Feature Component
<Fragment> / <>...</>
- Wraps multiple elements without adding extra nodes to the DOM.
- Keeps your UI clean and avoids unexpected wrappers.
<Suspense fallback={<Spinner />}>...</Suspense>
-
Used for components or data that take time to load.
-
Triggers only when using Suspense-aware features, like:
-
🛠️ Frameworks: Relay, Next.js
-
📦 Lazy-loaded components:
const LazyReviewCar = lazy(() => import("./components/cars/ReviewCar.js")); -
⏳ React's experimental
use()hook to read Promises
-
<StrictMode><App /></StrictMode>
-
Development-only tool (no effect in production).
-
Helps catch common issues early, like:
- Deprecated APIs
- Unexpected side effects
- Unsafe lifecycle methods
<Profiler id="Component" onRender={onRender}>...</Profiler>
-
Measures performance of a specific component.
-
onRendercallback example:const onRender = (id,phase,actualDuration,baseDuration,startTime,commitTime) => {// Log or analyze performance metrics};
Styles component
- Using sass/scss:
npm i sass - Both CSS & SASS can using as file or module
.custom-font-weight {
font-weight: bold;
}
.bg-red {
background: #ff0000;
}
import React from "react"; // To use JSX
import styleModule from "custom.module.css"; // import as module
import "custom.css"; // using css file
const Custom = ({ name }) => {
const customStyle = {
fontSize: "2rem",
color: "#333333",
};
// css module class will display like [filename]_[classname]__[hash]
return (
<div className="custom-font-weight" style={styleModule["bg-red"]}>
<span style={customStyle}>{"Hello " + name}</span>
</div>
);
};
Hooks
- Call them at the top level in the body of
- function component
- custom Hook
- Do not call Hooks
- inside conditions or loops.
- after a conditional return statement. NOTE HERE FOR NEXT PROJECT
- in event handlers.
- in class components.
- inside functions passed to useMemo, useReducer, or useEffect.
- inside try/catch/finally blocks.
- Hooks cannot be conditional
import { use<HookName> } from <'react'|'address-of-custom-hooks'>
useState
statechange after setState call -> component re-rendersetState(newState)|setState(prevState => { return newState })(use with callback)
import { useState } from "react";
function Car() {
// const [state, setState] = useState(initState | () => initState);
const [car, setCar] = useState({
brand: "Ford",
model: "Mustang",
year: "1964",
color: "red",
});
const toggleColor = () => {
setCar((previousState) => {
const color = previousState.color === "red" ? "blue" : "red";
return { ...previousState, color };
});
};
return (
<>
<h1>My {car.brand}</h1>
<p>
It is a {car.color} {car.model} from {car.year}.
</p>
<button type="button" onClick={toggleColor}>
Toggle color
</button>
</>
);
}
useDeferredValue
- Debouncing - wait for the user to stop typing (e.g. for a second) before updating the list.
- Throttling - update the list every once in a while (e.g. at most once a second).
useDeferredValueis better suited to optimizing rendering because it is deeply integrated with React itself and adapts to the user’s device.- Usage
- Showing stale content while fresh content is loading
- Deferring re-rendering for a part of the UI
import { useDeferredValue } from "react";
export default function App() {
const [filter, setFilter] = useState("");
const deferredFitler = useDeferredValue(filter);
return (
<div className="container mt-3">
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
type="text"
className="w-100"
/>
<ListAnimalMap filter={deferredFitler} />
</div>
);
}
useReducer
- Tracking complex state (like object) -> Same tech like React-Redux
- Custom handle state
- Syntax:
const [state, dispatch] = useReducer(reducer, initialState); - reducer:
const reducer = (currentState, action: { type, [any]?: value }) => { return newState } - Update:
dispatch({ type, [any]?: value })
import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "incremented_age": {
return {
name: state.name,
age: state.age + 1,
};
}
case "changed_name": {
return {
name: action.nextName,
age: state.age,
};
}
}
throw Error("Unknown action: " + action.type);
}
const initialState = { name: "Taylor", age: 42 };
export default function Form() {
const [state, dispatch] = useReducer(reducer, initialState);
function handleButtonClick() {
dispatch({ type: "incremented_age" });
}
function handleInputChange(e) {
dispatch({
type: "changed_name",
nextName: e.target.value,
});
}
return (
<>
<input value={state.name} onChange={handleInputChange} />
<button onClick={handleButtonClick}>Increment age</button>
<p>
Hello, {state.name}. You are {state.age}.
</p>
</>
);
}
useRef
- Persist value of component (not affect if component re-render)
- Used to access a DOM element directly
- Declare:
const elRef = useRef(initValue || undefined);. (Recommend usinginitValueif ref not element) - Add to element:
<element ref={elRef} /> - Value of ref
- Get:
elRef.current - Set:
elRef.current = any;
- Get:
useImperativeHandle
- Customize the handle exposed as a
ref useImperativeHandle(ref, createHandle, dependencies?)forwardRefis deprecated in React 19- If you can express something as a prop, you should not use a ref.
import React, { useRef, useImperativeHandle } from "react";
const ValidInput = ({ ref, ...props }) => {
const inputRef = useRef();
useImperativeHandle(
ref,
() => ({
addRandomNumber: (max = Number.MAX_SAFE_INTEGER) => {
inputRef.current.value = (Math.random() * max).toFixed(0);
},
}),
[]
);
return <input type="text" ref={inputRef} {...props} />;
};
export default function App() {
const pointingRef = useRef();
const onBtnClick = () =>
pointingRef.current && pointingRef.current.addRandomNumber();
return (
<div>
<ValidInput ref={pointingRef} />
<button type="button" onClick={onBtnClick}>
Random number
</button>
</div>
);
}
// Ref with clean up event listener
export default function App() {
return (
<div
ref={(node) => {
if (!node) return;
const handleClick = () => alert("Clicked!");
node.addEventListener("click", handleClick);
return () => {
node.removeEventListener("click", handleClick);
};
}}
>
Click me
</div>
);
}
useEffect & useLayoutEffect
- Perform side effect in component
- fetching data
- directly update DOM
- timers
- Syntax:
useEffect(<function>, [...dependencies]?)(React teams recommend)- Effect run 1st after component render
- Effect recall if dependencies has change, none if dependencies is []
useLayoutEffect- A version of
useEffectthat fires before the browser repaints the screen. - Only use in case need to calculate layout before show UI (tooltips, popover, ...)
- A version of
import { useState, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
// clean up function
return () => clearTimeout(timer);
}, [count]); // render when count change
return <h1>I've rendered {count} times!</h1>;
}
Apply for Component which can fetch data when mount to avoid refetch with StrictMode or useEffect has show dependency
<button type="button" onClick={() => setShow(!show)}>
Show modal
</button>;
{
show && <Modal show={show} onHide={() => setShow(false)} />;
}
import { useEffect } from "react";
import { createPortal } from "react-dom";
type ModalProps = {
show: boolean;
onHide: () => void;
};
const Modal: React.FC<ModalProps> = ({ show, onHide }) => {
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
(async () => {
try {
const res = await fetch(
"https://my-json-server.typicode.com/typicode/demo/posts",
{ signal }
);
if (!signal.aborted) {
if (res.ok) {
const data = await res.json();
console.log(data);
} else {
console.log("Status: ", res.status);
}
}
} catch (err) {
if (!signal.aborted) {
console.log(err);
}
}
})();
return () => {
abortController.abort();
};
}, [show]);
return createPortal(
<div className="custom-modal" onClick={onHide}>
Hello
</div>,
document.body
);
};
export default Modal;
useCallback & useMemo
- Problem: Component re-render when
- function & value inside (not state) re-render
- children use props includes that function & value re-render
=> Need to control re-render when it need with dependencies
useCallback- Using:
const memorizeFunction = useCallback(() => {}, [...dependencies]) - Avoid child component re-render if function of parent re-render
- Using:
useMemo- Using:
const memorizeValue = useMemo(() => any, [...dependencies])
- Using:
useContext
- Manage state globally
- State use in component that need it, avoid put parent props -> child props -> ... -> n-child props (prop drilling)
- Step 1 - Create
const Context = createContext(defaultValue?);- Context now is High Order Component
- defaultValue can put inside to get recommend for state (IDE Support)
- Step 2 - Wrap
<Context.Provider value={{ state1, setState1, state2, sampleArr, ..., any }}>{children}</Context.Provider>- All children inside
<Context.Provider>can use all props in value - value should be an object
- Step 3 - Get value
const { state1, state2 } = useContext(Context);- Put Context to
useContextto get correct value holder - Get only props need to using
import { createContext, use, useContext, useState } from "react";
const UserContext = createContext();
function Component1() {
const [user, setUser] = useState("Jesse Hall");
return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 />
</UserContext.Provider>
);
}
function Component2() {
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}
function Component3() {
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}
function Component4() {
// const user = useContext(UserContext);
const user = use(UserContext); // React 19 features
return (
<>
<h1>Component 4</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
useId
const uniqueId = useId();- UniqueId will create 1 time for Component when it render
- Use in case common Component (like Input) reuse more than 2 in parent component -> Avoid dupplicate Id when using
import { useId } from "react";
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label htmlFor={passwordHintId}>Password</label>
<input type="password" id={passwordHintId} />
</>
);
}
export default function App() {
return (
<>
<h2>Choose password</h2>
<PasswordField />
<h2>Confirm password</h2>s
<PasswordField />
</>
);
}
- Suspense +
lazy createContext-> Context (Context.Provider value=) ->useContext(Context)useRef-> putrefas props (React 19) ->useImperativeHandle
ReactDOM.createPortal
- Render some children into a different part of the DOM (drawer, modal, tooltips, ...)
createPortal(children, domNode, key?)
import { createPortal } from "react-dom";
export function Modal({ children }) {
return createPortal(children, document.body);
}
React Compiler
React Compiler is stable and included with React 19. It works by:
- Automatic Memoization: Inserts
useMemoanduseCallbackwhere needed - Zero-Config: Works out of the box for most applications
- Type-Safe: Fully compatible with TypeScript
- Incremental Adoption: Can be adopted gradually in existing codebases
- Build-Time Only: No runtime overhead, only compile-time analysis
Prerequisites
- React 17+ (React 19 recommended for best experience)
- Modern build tools (Babel, Vite, Next.js, etc.)
- For React 17/18: Additional
react-compiler-runtimepackage
Installation
Install React Compiler as a development dependency:
npm install -D babel-plugin-react-compiler@latest
# or
yarn add -D babel-plugin-react-compiler@latest
# or
pnpm install -D babel-plugin-react-compiler@latest
For React 17/18 projects, also install the runtime:
npm install react-compiler-runtime@latest
Configuration
Basic Setup
React Compiler works with most build tools. The plugin must run first in your Babel pipeline.
Babel
module.exports = {
plugins: [
"babel-plugin-react-compiler", // must run first!
// ... other plugins
],
};
Vite
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react({
babel: {
plugins: [["babel-plugin-react-compiler"]],
},
}),
],
});
Next.js
React Compiler is built into Next.js 15+:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
module.exports = nextConfig;
For Next.js 14 or custom configuration:
const ReactCompilerConfig = {
// optional configuration
};
module.exports = {
experimental: {
reactCompiler: ReactCompilerConfig,
},
};
React Router (Vite)
import { defineConfig } from "vite";
import babel from "vite-plugin-babel";
import { reactRouter } from "@react-router/dev/vite";
const ReactCompilerConfig = {
/* ... */
};
export default defineConfig({
plugins: [
reactRouter(),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ["@babel/preset-typescript"], // if using TypeScript
plugins: [["babel-plugin-react-compiler", ReactCompilerConfig]],
},
}),
],
});
Advanced Configuration
Compilation Modes
const config = {
// 'all': Compile all functions (default for React 19)
// 'annotation': Only compile functions with "use memo" directive
// 'infer': Intelligent detection (default for React 17/18)
compilationMode: "all",
};
React Version Targeting
const config = {
// For React 17/18 projects
target: "18", // or '17'
};
Source Filtering
const config = {
// Only compile specific directories
sources: (filename) => {
return filename.includes("src/components");
},
};
Error Handling
const config = {
// 'all': Fail build on any error (default)
// 'none': Skip problematic components
panicThreshold: "none",
};
ESLint Integration
React Compiler includes ESLint rules to identify optimization opportunities:
npm install -D eslint-plugin-react-hooks@latest
Add to your ESLint config:
module.exports = {
plugins: ["react-hooks"],
extends: [
"plugin:react-hooks/recommended-latest", // includes compiler rules
],
};
Verification
React DevTools
Components optimized by React Compiler show a "Memo ✨" badge in React DevTools.
Build Output
Check your compiled code for automatic memoization:
// Compiled output includes:
import { c as _c } from "react/compiler-runtime";
ESLint
Run ESLint to see which components can be optimized:
npx eslint src/
Opting Out
Component Level
Use the "use no memo" directive to skip optimization:
function ProblematicComponent() {
"use no memo";
// Component code here
}
File Level
Add a comment at the top of the file:
// eslint-disable-next-line react-hooks/rules-of-hooks
Troubleshooting
Common Issues
- Components not optimizing: Check ESLint for Rules of React violations.
- Build errors: Ensure the plugin runs first in your Babel config.
- React 17/18 issues: Install
react-compiler-runtimeand settargetconfig. - TypeScript errors: Update to latest TypeScript version.
Debugging
Enable verbose logging:
const config = {
logger: {
logEvent(filename, event) {
console.log("Compiler event:", filename, event);
},
},
};
Migration Guide
From Manual Memoization
Remove unnecessary useMemo, useCallback, and React.memo:
// Before
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a), [a, b]);
// After - let the compiler handle it
const value = computeExpensiveValue(a, b);
const callback = () => doSomething(a);
Gradual Adoption
Start with specific directories:
const config = {
compilationMode: "annotation",
sources: (filename) => filename.includes("src/new-components"),
};
Then add "use memo" directives to components you want optimized:
function MyComponent() {
"use memo";
// Component code
}
Performance Benefits
- Automatic memoization of expensive calculations
- Reduced re-renders through intelligent dependency tracking
- Smaller bundle size (no manual memoization code)
- Better developer experience (less boilerplate)
Limitations
- Only optimizes components following the Rules of React
- Cannot optimize code that violates React's constraints
- Some advanced patterns may require manual memoization
- Not a silver bullet for all performance issues (profiling still recommended)