React
using docker
- run using docker
docker run -it --name appName -p 3000:3000 -d imageName
- open bash in docker
docker exec -it appName bash
setState
import React from "react";
export default class App extends React.Component {
state = {
count: 0,
};
handleClick = () => {
// method 1
const {count} = this.state;
this.setState({count: count + 1});
// method 2
this.setState((prevState) => ({count: prevState.count + 1}));
};
render() {
const {count} = this.state;
return <div onClick={this.handleClick}>{count}</div>;
}
}
import React, {useState} from "react";
export default function App() {
const [count, setCount] = useState(0); // 0 = initial state
const handleClick = () => {
setCount(count + 1);
};
return <div onClick={handleClick}>{count}</div>;
}
handle multiple states
- example 1
import React from "react";
export default class App extends React.Component {
state = {
name1: "",
name2: "",
};
handleChange = (e) => {
this.setState({[e.target.name]: e.target.value}); // requires the name attribute to be declared at the html tag
};
render() {
const {name1} = this.state;
return <input onChange={this.handleChange} name={name1} value={name1} />;
}
}
import React, {useState} from "react";
export default function App() {
const [state, setState] = useState({
name1: "",
name2: "",
});
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
return (
<input onChange={handleChange} value={state.name1} name={state.name1} />
);
}
- example 2
import React from "react";
export default class App extends React.Component {
state = {
name1: "",
name2: "",
};
handleChange = (prop) => (e) => this.setState({[prop]: e.target.value});
render() {
const {name1} = this.state;
return <input onChange={this.handleChange("name1")} value={name1} />;
}
}
import React, {useState} from "react";
export default function App() {
const [state, setState] = useState({
name1: "",
name2: "",
});
const handleChange = (prop) => (e) => {
setState({
...state,
[prop]: e.target.value,
});
};
return <input onChange={handleChange("name1")} value={state.name1} />;
}
Batch state updates (not required in React 18)
- wrong way
import React, {useState} from "react";
export default function App() {
const [count1, setCount1] = useState(0); // 0 = initial state
const [count2, setCount2] = useState(0); // 0 = initial state
const handleClick = () => {
// React doesn't automatically batch updates
// react will re-render the UI twice for this case
Promise.resolve().then(() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
}
}
return (<div onClick={handleClick}>{count}</div>);
}
- correct way
import React, {useState} from "react";
import {unstable_batchedUpdates} from "react-dom";
export default function App() {
const [count1, setCount1] = useState(0); // 0 = initial state
const [count2, setCount2] = useState(0); // 0 = initial state
const handleClick = () => {
// react will re-render the UI once
Promise.resolve().then(() => {
unstable_batchedUpdates(() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
})
}
}
return (<div onClick={handleClick}>{count}</div>);
}
Lifecycle vs Hooks
ComponentDidMount
import React from "react";
export default class App extends React.Component {
ComponentDidMount() {
doSomething();
}
render() {
return <div></div>;
}
}
useEffect
import React, {useEffect} from "react";
export default function App() {
useEffect(() => {
doSomething();
}, []);
return <div></div>;
}
ComponentDidUpdate
import React from "react";
export default class App extends React.Component {
ComponentDidUpdate(prevProps, prevStates) {
if (prevProps.something !== this.props.something) {
doSomething();
}
}
render() {
const {something} = this.props;
return <div>{something}</div>;
}
}
useEffect
import React, {useEffect} from "react";
export default function App(props) {
const {something} = props;
useEffect(() => {
doSomething();
}, [something]);
return <div>{something}</div>;
}
ComponentWillUnmount
import React from "react";
export default class App extends React.Component {
ComponentWillUnmount() {
doSomething();
}
render() {
return <div></div>;
}
}
useEffect
import React, {useEffect} from "react";
export default function App(props) {
useEffect(() => {
return doSomething; // must return the action for component when unmounted, must be an uncalled function
}, []); // this does not matter
return <div></div>;
}
ShouldComponentUpdate
- Should be used if a function renders the same result given the same props and states (mainly for performance optimization)
method 1: declaring in the shouldComponentUpdate method
import React from "react";
export default class App extends React.Component {
state = {
name: "",
};
shouldComponentUpdate(nextProps, nextStates) {
if (nextProps.something !== this.props.something) {
return true; // allow rerendering
}
if (nextStates.name !== this.state.name) {
return true;
}
return false; // do not allow rerendering
}
handleChange = (e) => {
this.setState({name: e.target.value});
};
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.name);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.name} />
<input type="submit" value="Submit" />
</form>
);
}
}
method 2: use PureComponent (preferred method)
import React from "react";
export default class App extends React.PureComponent {
state = {
name: "",
};
handleChange = (e) => {
this.setState({name: e.target.value});
};
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.name);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.name} />
<input type="submit" value="Submit" />
</form>
);
}
}
method 3: use memo for functional components
import React, {useState, memo} from "react";
function App() {
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(name);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} value={name} />
<input type="submit" value="Submit" />
</form>
);
}
// return value is the inverse of shouldComponentUpdate
function areEqual(prevProps, nextProps) {
if (nextProps.name !== prevProps.name) {
return false; // allow rerendering
}
return true; // do not allow rerendering
}
export default memo(App, areEqual);
useMemo
- Memoization: cache result of function call
- Warning: do not prematurely optimize performance, use only as needed for expensive calculations
- gives you referential equality between renders for values
- will only recompute the memoized value when 1 of the dependencies has changed
- help to avoid expensive calculations on every render
- if no dependency array is provided, a new value will be computed on every render
- calls its function and returns the result value
import React, {useState, useMemo} from "react";
const App = () => {
const [count, setCount] = useState(0);
const expensiveCount = useMemo(() => {
return count ** 2;
}, [count]);
return <>{expensiveCount}</>;
};
export default App;
useCallback for functional methods
- Memoization: cache result of function call
- Warning: do not prematurely optimize performance, use only as needed for expensive calculations
- gives you referential equality between renders for functions
- returns its memoized function when the dependencies change
- helps prevent unneccessary renders of the children because the children will always be using the same function object
- e.g. shouldComponentUpdate
import React, {useState, useCallback } from "react";
import DisplayCount from "./DisplayCount";
const App = () => {
const [count, setCount] = useState(0);
const showCount = useCallback(() => alert(`Count ${count}`)), [count]);
return (
<>
<DisplayCount handleDisplay={showCount} />
</>
);
};
export default App;
useContext
- allows us to work with react context api, which allows us to share data without passing props
- A component calling useContext will always re-render when the context value changes
- optimize with memoization if component computation is expensive
set state
import {createContext} from "react";
export default MoodContext = createContext(null);
share state
import React, {useState} from "react";
import MoodContext from "./MoodContext";
import MoodEmoji from "./MoodEmoji";
export default function App() {
const [moodState] = useState({
happy: "happy",
sad: "sad",
});
return (
<MoodContext.Provider value={moodState}>
<MoodEmoji />
</MoodContext.Provider>
);
}
use state
- using
XxxContext.Consumer
import React from "react";
import MoodContext from "./MoodContext";
export default function MoodEmoji() {
return (
<MoodContext.Consumer>{(mood) => <p>{mood.happy}</p>}</MoodContext.Consumer>
);
}
- using
useContext
import React, {useContext} from "react";
import MoodContext from "./MoodContext";
export default function MoodEmoji() {
const mood = useContext(MoodContext);
return <p>{mood.happy}</p>;
}
use generic context
- GenericContext file
import React, { Dispatch, createContext, SetStateAction, useState, PropsWithChildren } from "react;
export default function createCtx<A>(defaultValue: A) {
type UpdateType = Dispatch<
SetStateAction<typeof defaultValue>
>;
const defaultUpdate: UpdateType = () => defaultValue;
const ctx = createContext({
state: defaultValue,
update: defaultUpdate,
});
function Provider(props: PropsWithChildren<{}>) {
const [state, update] = useState(defaultValue);
return <ctx.Provider value={{ state, update }} {...props} />;
}
return [ctx, Provider] as const;
}
- set state with SampleContext file
import createCtx from "./GenericContext";
export const [SampleContext, SampleProvider] = createCtx("default value");
- share state
import React from "react";
import {SampleProvider} from "./SampleContext";
import SampleFeature from "./SampleFeature";
export default function App() {
return (
<SampleProvider>
<SampleFeature />
</SampleProvider>
);
}
- use state with useContext
import React, {useContext} from "react";
import SampleContext from "./SampleContext";
export default function SomeFeature() {
const {state: myStateName, update: handleUpdate} = useContext(SampleContext);
return <p onClick={() => handleUpdate("test")}>{myStateName}</p>;
}
useReducer
- an alternative to useState
- returns an array of 2 values
- first value is the state, second value is dispatch function
- returns an array of 2 values
- it uses the redux pattern and has a different way to manage state
- instead of updating the state directly, action is dispatched that goes to the reducer
- the reducer determines how to compute the next state
- useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values
- or when the next state depends on the previous one
- useReducer also lets you optimize performance for components that trigger deep updates
- because you can pass dispatch down instead of callbacks
- React guarantees that dispatch function identity is stable and won’t change on re-renders
- This is why it’s safe to omit from the useEffect or useCallback dependency list
Single state
import React, {useReducer} from "react";
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
throw new Error();
}
}
export default function App() {
const [count, dispatch] = useReducer(reducer, initialState);
const handleDecrement = () => dispatch({type: "decrement"});
return (
<>
Count: {count}
<button onClick={handleDecrement}>-</button>
</>
);
}
Multiple states
import React, {useReducer} from "react";
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
throw new Error();
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleDecrement = () => dispatch({type: "decrement"});
return (
<>
Count: {state.count}
<button onClick={handleDecrement}>-</button>
</>
);
}
Lazy initialization
import React, {useReducer} from "react";
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case "increment":
return {count: state.count + 1};
case "decrement":
return {count: state.count - 1};
case "reset":
return init(action.payload);
default:
throw new Error();
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: "reset", payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: "decrement"})}>-</button>
<button onClick={() => dispatch({type: "increment"})}>+</button>
</>
);
}
useImperativeHandle
- customizes the instance value that is exposed to the parent components when using ref
- imperative code using refs should be avoided in most cases
useImperativeHandle
should be used with forwardRef- it is mainly used to change the behavior of the exposed ref, which is a rare use case
- use cases
- if you build a reusable component library, you may need to get access to the underlying DOM element
- and then forward it so that it can be accessed by the consumers of the component library
- if you build a reusable component library, you may need to get access to the underlying DOM element
import React, {forwardRef, useRef} from "react";
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
const activateFocus = () => {
console.log("focused");
inputRef.current.focus();
};
React.useImperativeHandle(ref, () => ({
activateFocus,
}));
return (
<div>
<input ref={inputRef} />
<button onClick={activateFocus}>Focus from children</button>
</div>
);
});
export default FancyInput;
import React, {useRef} from "react";
import FancyInput from "./FancyInput";
function App() {
const inputRef = useRef();
return (
<div className="App">
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.activateFocus()}>
Focus from parent
</button>
</div>
);
}
export default App;
useLayoutEffect
- identical to useEffect, but it fires synchronously after all DOM mutations
- difference is that the callback will run after render but before the actual updates have been displayed to the screen
- it will block visual UI updates until callback is completed
- use cases
- situations when you need to calculate something in the UI before the DOM is visually updated
- If you’re migrating code from a class component
- useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate
- If you use server rendering
- neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded
- To fix this, either move that logic to useEffect (if it isn’t necessary for the first render)
- or delay showing that component until after the client renders (if the HTML looks broken until useLayoutEffect runs)
- neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded
import React, {useRef, useLayoutEffect} from "react";
function App() {
const myBtn = useRef(null);
useLayoutEffect(() => {
const rect = myBtn.current.getBoundingClientRect();
console.log(rect.height);
});
return <button ref={myBtn}>button</button>;
}
export default App;
useTransition (React 18)
- Returns a stateful value for the pending state of the transition, and a function to start it
startTransition
lets you mark updates in the provided callback as transitionsisPending
indicates when a transition is active to show a pending state- Updates in a transition yield to more urgent updates such as clicks
- Updates in a transitions will not show a fallback for re-suspended content
- This allows the user to continue interacting with the current content while rendering the update
- Original code
import {useState, useMemo} from "react";
function ProductList({products}) {
return (
<ul>
{products.map((product) => (
<li>{product}</li>
))}
</ul>
);
}
export default function App() {
const [filterTerm, setFilterTerm] = useState("");
const dummyProducts = useMemo(() => {
const products = [];
for (let i = 0; i < 10000; i++) {
products.push(`Product ${i + 1}`);
}
return products;
}, []);
const filteredProducts = useMemo(() => {
if (!filterTerm) {
return dummyProducts;
}
return dummyProducts.filter((product) => product.includes(filterTerm));
}, [dummyProducts, filterTerm]);
const updateFilterHandler = (event) => {
setFilterTerm(event.target.value);
};
return (
<div id="app">
<input type="text" onChange={updateFilterHandler} />
<ProductList products={filteredProducts} />
</div>
);
}
- using the hook
import {useState, useMemo, useTransition} from "react";
import "./styles.css";
function ProductList({products}) {
return (
<ul>
{products.map((product) => (
<li>{product}</li>
))}
</ul>
);
}
export default function App() {
const [isPending, startTransition] = useTransition();
const [filterTerm, setFilterTerm] = useState("");
const dummyProducts = useMemo(() => {
const products = [];
for (let i = 0; i < 10000; i++) {
products.push(`Product ${i + 1}`);
}
return products;
}, []);
const filteredProducts = useMemo(() => {
if (!filterTerm) {
return dummyProducts;
}
return dummyProducts.filter((product) => product.includes(filterTerm));
}, [dummyProducts, filterTerm]);
const updateFilterHandler = (event) => {
startTransition(() => {
setFilterTerm(event.target.value);
});
};
return (
<div id="app">
<input type="text" onChange={updateFilterHandler} />
{isPending && <p style={{color: "red"}}>Updating List...</p>}
<ProductList products={filteredProducts} />
</div>
);
}
useDeferredValue (React 18)
- accepts a value and returns a new copy of the value that will defer to more urgent updates
- If the current render is the result of an urgent update
- like user input, React will return the previous value and then render the new value after the urgent render has completed
- useDeferredValue only defers the value that you pass to it
- If you want to prevent a child component from re-rendering during an urgent update, you must also memoize that component with React.memo or React.useMemo
- Original code refer to useTransition original code example
- using the hook
import {useState, useMemo, useDeferredValue} from "react";
function ProductList({products}) {
const deferredProducts = useDeferredValue(products);
return (
<ul>
{deferredProducts.map((product) => (
<li>{product}</li>
))}
</ul>
);
}
export default function App() {
const [filterTerm, setFilterTerm] = useState("");
const dummyProducts = useMemo(() => {
const products = [];
for (let i = 0; i < 10000; i++) {
products.push(`Product ${i + 1}`);
}
return products;
}, []);
const filteredProducts = useMemo(() => {
if (!filterTerm) {
return dummyProducts;
}
return dummyProducts.filter((product) => product.includes(filterTerm));
}, [dummyProducts, filterTerm]);
const updateFilterHandler = (event) => {
setFilterTerm(event.target.value);
};
return (
<div id="app">
<input type="text" onChange={updateFilterHandler} />
<ProductList products={filteredProducts} />
</div>
);
}
useId (React 18)
- a hook for generating unique IDs that are stable across the server and client, while avoiding hydration mismatches
- is not for generating keys in a list
- Keys should be generated from your data
- example
import React, {useId} from "react";
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input id={id} type="checkbox" name="react" />
</>
);
}
export default Checkbox;
Custom Hooks
useDebugValue
- used to display a label for custom hooks in React DevTools
import {useState, useEffect, useDebugValue} from "react";
export default function useCount() {
const [count, setCount] = useState();
useEffect(() => {
doSomething();
return doAnotherThing();
}, [count]);
const handleClick = () => {
if (count === undefined) {
setCount(0);
} else {
setCount(count + 1);
}
};
useDebugValue(count ?? "haven't started counting");
return {
count,
handleClick,
};
}
import React from "react";
import useCount from "./useCount";
export default function App() {
const {count, handleClick} = useCount();
return <div onClick={handleClick}>count {count}</div>;
}
Forms
Uncontrolled form
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.name = React.createRef();
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.name.current.value);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input ref={this.name} />
<input type="submit" value="Submit" />
</form>
);
}
}
using useRef
- allows you to create a mutable plain javascript object that keeps the same reference between renders
- can be used when there is a value that changes similar to setState
- however, it does not trigger a re-render if the value changes
- common use is to grab native HTML elements from the DOM
- this hook should be used when you need to grab an element from the DOM
import React, { useRef } from "react";
export default function App() => {
const name = useRef();
const handleSubmit = e => {
e.preventDefault();
console.log(name.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={name} />
<input type="submit" value="Submit" />
</form>
);
}
Controlled form
import React from "react";
export default class App extends React.Component {
state = {
name: "",
};
handleChange = (e) => {
this.setState({name: e.target.value});
};
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.name);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.name} />
<input type="submit" value="Submit" />
</form>
);
}
}
import React, { useState } from "react";
export default function App() => {
const [name, setName] = useState("");
const handleChange = e => {
setName(e.target.value);
}
const handleSubmit = e => {
e.preventDefault();
console.log(name);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} value={name} />
<input type="submit" value="Submit" />
</form>
);
}