React
Life Cycle

Re-render

There is element in the index.html which id is root and render the react dom element
The react dom object itself is a virtual dom which is lightweight, which looks like:
import React from 'react'
import { createRoot } from 'react-dom/client'
const spanElement = React.createElement('span', {
children: '123'
});
console.log(spanElementElement);
console.log(<span>123</span>);
/*
{
"type":"span",
"key":null,
"ref":null,
"props":{"children":"123"},
"_owner":null,
"_store":{}
}
*/
createRoot(document.getElementById('root')!).render(
spanElement
)
For every state change, the page will be re-rendered again, the state , variable , function, useEffect in the render scope will be independent to previous one
For each rendering, the functional component will be returned and then triggered useEffect
The virtual dom will be saved in memory and will be compared with the previous virtual dom with its internal differing algorithm, and just update the changed part on the real dom which is into dom which id is root so as to increase the efficiency

Hook
useState
When you call
setState
, React schedules the state update and marks the component for a re-render. This scheduling is asynchronousReact batched state updates in event handlers to reduce the number of re-rendering
// number is a state, setNumber is a setter method
const [number, setNumber] = useState(1);
// put the value into setter to set the value directly
setNumber(2);
// put the update function into setter
// to set the value which is based on previous value
setNumber(prevNum => prev + 1);
// Not recommended
setNumber(number + 1);
function A () {
const [count, setCount] = useState(4);
setCount(count + 1);
setCount(count + 1);
console.log('A: ', count) // ?
}
function B () {
const [count, setCount] = useState(4);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
console.log('B: ', count) // ?
}
// Answer
// A: 5
// B 6
function init () {
console.log('run function');
return 4;
}
// Run Everytime when changing the state
const [count, setCount] = useState(4);
const [count, setCount] = useState(init());
// Run only the very first time when your component render
const [count, setCount] = useState(() => init());
useEffect
It is used to listen the dependencies change, e.g: state, and execute the side effect, which is equal to lifecycle event of class component
When the component is mounted, the side effect will be triggered once first.
When component is unmounted, the return function will be triggered and based on the previous useEffect. After the new user interface finish re-rendering, , and finally trigger the new useEffect function
"use client"
import { Button,} from "@mui/material";
import { useEffect,useState } from "react";
export default function Home() {
const [num, setNum] = useState<number>(0);
useEffect(()=>{
console.log("update" , num);
return ()=> {
console.log("return function", num);
}
},[num])
// update 0
// return function 0
// update 1
return (
<div>
<Button variant="contained" onClick={()=>{
setNum((prev)=>prev+1)
}} >Hello world</Button>
</div>
);
}
The function of useEffect will always be triggered in every re-rendering as there is no dependency array
useEffect(async () => {
const { data } = await axios.get(
"https://sampleapis.com/futurama/api/info",
{}
);
console.log("data", data);
});
The function of useEffect will only be triggered once as the dependency array is empty, there will not have any dependency change to trigger useEffect
useEffect(async () => {
const { data } = await axios.get(
"https://sampleapis.com/futurama/api/info",
{}
);
console.log("data", data);
},[]);
The function of useEffect will be triggered due to the change of the state
useEffect(async () => {
const { data } = await axios.get(
"https://sampleapis.com/futurama/api/info",
{}
);
console.log("data", data);
}, [state]);
Case Study 1
As the state and variable in render scope is put in the useEffect, it is not recommended to put an empty array as it may cause potential bug
const App = () =>{
const [count , setCount] = useState(0);
const test = 2;
// Wrong
useEffect(()=>{
setCount(count+1);
test = 3;
},[])
// Correct
useEffect(()=>{
setCount(count+1);
}, [count, test])
}
Case Study 2
As the function in render scope is put in useEffect, it is recommended to put the function in the dependency array
As we do not want the useEffect always be triggered as the function is always be changed in every re-rendering, so we use useCallback to save the function except the dependency is changed
const App = () =>{
const [count , setCount] = useState(0);
// Wrong
useEffect(()=>{
addCount();
}, [])
const addCount = () => {
setCount(count+1);
}
// Correct
useEffect(() => {
addCount();
}, [addCount])
const addCount = useCallback(() => {
setCount(count+1);
}, [count])
}
Case Study 3
As async function have the delay, we cannot make sure of order of the setState. In order to prevent from set the wrong data which is based on the the value or state in previous rendering, it is recommended to make return function to stop setting wrong value after the state is updated
function Article({ id }) {
const [article, setArticle] = useState(null);
useEffect(() => {
let didCancel = false;
async function fetchData() {
const article = await API.fetchArticle(id);
if (!didCancel) {
setArticle(article);
}
}
fetchData();
return () => {
didCancel = true;
};
}, [id]);
// ...
}
UseContext
Share all the data from component with upper layer to components with lower layer without passing props
// Mother.js
import React, { createContext } from "react";
import Son from "./Son";
type ParentContext = {
data:string
setData: (data:string) => void
}
export const ParentContext = createContext<ParentContext>({data: ""});
const Mother = () => {
const [data, setData] = useState("Mother");
return (
<ParentContext.Provider value={{data,setData}}>
<Son />
</ParentContext.Provider>
);
};
export default Mother;
// Son.js
import React, { useContext } from "react";
import { ParentContext } from "./context";
const Son = () => {
const {data,setData} = useContext(ParentContext);
return <span>{data}</span>;
};
export default Son;
useReducer
It is an advanced version of useState, since the setting function of the useState is only allow one situation and one return value. But useReducer can return different base on the the type, it is more suitable to handle more complicated logic
// 一個 Reducer 用來描述根據指令執行對應的動作,會回傳一個新的 state 物件,是個純函式
const todoReducer = (state, action) => {
// 判斷指令
switch (action.type) {
case 'ADD_TODO':
let newKey = 1
if (state.list.length !== 0){
newKey = state.list[state.list.length - 1].listKey + 1
}
return {
...state,
list: [...state.list, { listKey: newKey, name: action.payload.name }]
}
default:
return state
}
}
// 初始資料,為 state 的初始值,通常會在這裡把結構描述完整
const initState = {
list: [{ listKey: 1, name: 'first' }]
}
const Todo = () => {
const [listName, setListName] = useState('')
/*
使用 useReducer 將 Reducer 和 初始 State 個別傳入,
會回傳一個陣列,在此使用解構賦值將陣列的位置 0 及 1 分別放到 state 和 dispatch 中,
state 會隨著 Reducer 會傳的 state 做改變,
dispatch 是用來和 Reducer 溝通的 Function 。
*/
const [state, dispatch] = useReducer(todoReducer, initState)
const addTodo = () => {
//使用 dispatch 呼叫 Reducer 處理 state ,這個參數會被傳進 Reducer 的 action 中
dispatch({ type: 'ADD_TODO', payload: { name: listName } })
setListName('')
}
return (
<div>
<input value={listName}
onChange={e => setListName(e.target.value)} />
<input type='button' value='新增' onClick={addTodo} />
{state.list.map((list) => {
return (
<p key={list.key}>
<input type='button' value='移除'
onClick={() => { removeTodo(list.listKey) }} />
{list.name}
</p>
)
})}
</div>
)
}
useContext/useReducer vs Redux
useContext/useReducer : it is easier to setup and more suitable to put variables with low-frequency update on it , since every time the variable passed by useContext, the area inside the provider will be re-rendered
Redux: it is more complicated to setup, and middleware is allowed to use to handle async function. each component will subscribe (add listener) to their connected state. If the correspondent state is changed, the listener will be triggered, so that only that component will be re-rendered but not whole of the area inside the provider
useCallback
When the component is re-rendered, the functions will also be re-rendered. The purpose of useCallback is return the memorized callback function until the one of the dependency is changed.
const [delta, setDelta] = useState(1);
const [c, setC] = useState(0);
// the function will keep the same, which means delta always be 1,
// and listen to the dependency c , if c is changed ,
// the function will be re-rendered and updated
const incrementDeltaWithCallBack = useCallback(() => {
setDelta(delta + 1);
console.log("increaseDelta");
}, [c]);
const incrementWithCallBack = useCallback(() => {
setC(c + 1);
console.log("increment");
}, [delta]);
useMemo
When the component is re - rendered, the variable will also be re-rendered. The purpose of useMemo is return the memorized value until the one of the dependency is changed.
const [delta, setDelta] = useState(1);
// even setDelta is triggered, but value of testMemo will
// be the same, always be 1, will not be re-rendered
const testMemo = useMemo(() => delta, []);
useMemo vs useCallback
The effect of useMemo can equal to useCallback in this case
const incrementDeltaWithCallBack = useCallback(() => {
setDelta((delta) => delta + 1);
console.log("increaseDelta");
}, [delta]);
const incrementDeltaWithMemo = useMemo(() => () => {
setDelta((delta) => delta + 1);
console.log("increaseDelta");
}, [delta]);
The drawback of abusing useMemo/ useCallback
The time of the first time re-rendering will be much larger as it memorizes the value/function
The ram consumed in the browser will be much larger
The condition of using useMemo/ useCallback
The variable require long time to be rendered , for example : have a larger loop and return the result
The function is called in useEffect
useRef
The current property will be initialized by passing the argument , and if the current is changed, it will be saved, but the component will not be re-rendered, it can be used to store the value which is not related to the ui
const testRef = useRef(0);
const test = () => {
console.log(testRef.current); // 0
testRef.current++;
return (<div>{testRef.current}</div>)
// next time testRef.current will be 1 ...
// but the ui still show 0
}
The reference object can be acted as the ref of the element, so that the current property will be corresponded to the html element. We can make use good use of the reference to manipulate the element, such as focus
const App = () => {
const testRef = useRef();
const test = () => {
console.log(testRef.current); // <input..../>
testRef.current.focus();
}
return (
<div>
<input ref={testRef} type="text"/>
</div>
)
}
useImperativeHandle
To lets you customize the handle exposed as a ref
Mainly for exposing the child state, method to the parent or your own method
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
// to apply the method passed by child component
ref.current.focus();
}
return (
<form>
<MyInput placeholder="Enter your name" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
export default MyInput;
useLayoutEffect
useLayoutEffect
is a version ofuseEffect
that fires before the browser repaints the screen, to enhance the user experience and the shift of component
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Re-render now that you know the real height
}, []);
// The component only render once when using layout effect
// useLayoutEffect: render with height
// useEffect: render with 0 -> render with height
}
The code inside
useLayoutEffect
and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow. When possible, preferuseEffect
.
useTransition
The function you pass to
startTransition
must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as Transitions.The hook can be used prioritize the state update . A state update marked as a Transition will be interrupted by other state updates and marked as lower priority
If there are multiple ongoing Transitions, React currently batches them together.
The state update inside the startTransition will be non-blocking, which means that the state update will not be blocked by heavy computation and be freeze, it will interrupt the slow rendering immediately
import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();
// Urgent: Show what was typed
setInputValue(input);
// Mark any non-urgent state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setSearchQuery(input);
});
useDeferredValue
The deferred value is considered as a low priority, its deferred “background” rendering is interruptible even if the deferred value is updated.
For example, if you type into the input again, React will abandon it and restart with the new value. Also, network request is pending, the rendering is also be suspended
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
useDeferredValue
lets you prioritize updating the input (which must be fast) over updating the result list (which is allowed to be slower)
useOptimistic (React 19)
It provides a function that takes the current state and the input to the action, and returns the optimistic state to be used while the action is pending.
The state is called the “optimistic” state because it is usually used to immediately present the user with the result of performing an action, even though the action actually takes time to complete.
import { useOptimistic, useState, useRef } from "react";
import { deliverMessage } from "./actions.js";
function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get("message"));
formRef.current.reset();
await sendMessage(formData);
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true
}
]
);
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
export default function App() {
const [messages, setMessages] = useState([
{ text: "Hello there!", sending: false, key: 1 }
]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get("message"));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}
useActionState (React 19)
It pass
useActionState
an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state and pending state
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
Custom Hook
Can do a custom hook and apply to different component instead of making high order component

Last updated
Was this helpful?