SaigonTech Vault
Loại: knowledge-doc 2026-09-29 active #wiki-import

Link document:

React in w3school: https://www.w3schools.com/react/default.asp

redux: https://redux.js.org/tutorials/fundamentals/part-1-overview

demo: https://github.com/lbthucsgt/react-redux-tutorial

Class component and function component

# class component
class Car extends React.Component {
  render() {
    return <h2>Hi, I am a Car!</h2>;
  }
}

# function component
function Car() {
  return <h2>Hi, I am a Car!</h2>;
}

Từ React version  16.8, sử dụng hook thay vì class component. Nên bài viết này tập trung vào function component.

React Props

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_props_pass2

function Car(props) {
  return <h2>I am a { props.brand }!</h2>;
}

function Garage() {
  const carName = "Ford";
  return (
    <>
      <h1>Who lives in my garage?</h1>
      <Car brand={ carName } />
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Garage />);

React Event

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_events_handler

function Football() {
  const shoot = () => {
    alert("Great Shot!");
  }

  return (
    <button onClick={shoot}>Take the shot!</button>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Football />);

# passing argument

function Football() {
  const shoot = (a) => {
    alert(a);
  }

  return (
    <button onClick={() => shoot("Goal!")}>Take the shot!</button>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Football />);

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_events_event

# passing event

function Football() {
  const shoot = (a, b) => {
    alert(b.type);
    /*
    'b' represents the React event that triggered the function,
    in this case the 'click' event
    */
  }

  return (
    <button onClick={(event) => shoot("Goal!", event)}>Take the shot!</button>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Football />); 

React Lists

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_list1

function Car(props) {
  return <li>I am a { props.brand }</li>;
}

function Garage() {
  const cars = ['Ford', 'BMW', 'Audi'];
  return (
    <>
      <h1>Who lives in my garage?</h1>
      <ul>
        {cars.map((car) => <Car brand={car} />)}
      </ul>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Garage />);

link: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_lists_keys

# Keys
Keys allow React to keep track of elements. This way, if an item is updated or removed, 
only that item will be re-rendered instead of the entire list.
Keys need to be unique to each sibling. But they can be duplicated globally.

function Car(props) {
  return <li>I am a { props.brand }</li>;
}

function Garage() {
  const cars = [
    {id: 1, brand: 'Ford'},
    {id: 2, brand: 'BMW'},
    {id: 3, brand: 'Audi'}
  ];
  return (
    <>
      <h1>Who lives in my garage?</h1>
      <ul>
        {cars.map((car) => <Car key={car.id} brand={car.brand} />)}
      </ul>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Garage />);

React Form

Document: https://www.w3schools.com/react/react_forms.asp

Form will work as normal, the form will submit and the page will refresh.
But this is generally not what we want to happen in React.
We want to prevent this default behavior and let React control the form.

link: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_forms_submit

import { useState } from 'react';
import ReactDOM from 'react-dom/client';

function MyForm() {
  const [name, setName] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`The name you entered was: ${name}`)
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Enter your name:
        <input 
          type="text" 
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </label>
      <input type="submit" />
    </form>
  )
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MyForm />);

React Hook

Hooks were added to React in version 16.8.
Hooks allow function components to have access to state and other React features. 
Because of this, class components are generally no longer needed.

You must import Hooks from react.

There are 3 rules for hooks:      
    Hooks can only be called inside React function components.     
    Hooks can only be called at the top level of a component.     
    Hooks cannot be conditional

useState Hook

The React useState Hook allows us to track state in a function component.
State generally refers to data or properties that need to be tracking in an application.

link: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_usestate_update

# Update State

import { useState } from "react";
import ReactDOM from "react-dom/client";

function FavoriteColor() {
  const [color, setColor] = useState("red");

  return (
    <>
      <h1>My favorite color is {color}!</h1>
      <button
        type="button"
        onClick={() => setColor("blue")}
      >Blue</button>
    </>
  )
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<FavoriteColor />);

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_usestate4

import { useState } from "react";
import ReactDOM from "react-dom/client";

function Car() {
  const [car, setCar] = useState({
    brand: "Ford",
    model: "Mustang",
    year: "1964",
    color: "red"
  });

  const updateColor = () => {
    setCar(previousState => {
      return { ...previousState, color: "blue" }
    });
  }

  return (
    <>
      <h1>My {car.brand}</h1>
      <p>
        It is a {car.color} {car.model} from {car.year}.
      </p>
      <button
        type="button"
        onClick={updateColor}
      >Blue</button>
    </>
  )
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Car />);

useEffect Hooks

- useEffect Hook cho phép bạn gọi side effect(phản ứng phụ) trong componenet.
- ví dụ về side effect là: fetching data, directly updating the DOM, and timers
- cú pháp: useEffect(<function>, <dependency>)

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_useeffect_settimeout

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  });

  return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

- ví dụ này count render liên tục thay vì render một lần 
- đó là vì useEffect runs on every render. Khi count changes, a render happens, which then triggers another effect.
- để tránh như vậy cần tham khảo cách phía dưới

useEffect(() => {
  // Runs on every render
});

useEffect(() => {
  // Runs only on the first render
}, []);

useEffect(() => {
  // Runs on the first render
  // And any time any dependency value changes
}, [prop, state]);

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_useeffect_settimeout3

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Counter() {
  const [count, setCount] = useState(0);
  const [calculation, setCalculation] = useState(0);

  useEffect(() => {
    setCalculation(() => count * 2);
  }, [count]); // <- add the count variable here. Khi count thay đổi thì hàm useEffect được gọi lại 

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>Calculation: {calculation}</p>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_customhook1

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

const Home = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos")
      .then((res) => res.json())
      .then((data) => setData(data));
  }, []);

  return (
    <>
      {data &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>;
        })}
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Home />);

useReducer

The useReducer Hook is similar to the useState Hook.
It allows for custom state logic.
If you find yourself keeping track of multiple pieces of state that rely on complex logic, useReducer may be useful

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_usereducer

import React from 'react'
import { useReducer } from "react";

const initialTodos = [
    {
        id: 1,
        title: "Todo 1",
        complete: false,
    },
    {
        id: 2,
        title: "Todo 2",
        complete: false,
    },
];

const reducer = (state, action) => {
    switch (action.type) {
        case "COMPLETE":
            return state.map((todo) => {
                if (todo.id === action.id) {
                    return { ...todo, complete: !todo.complete };
                } else {
                    return todo;
                }
            });
        default:
            return state;
    }
};

const Test = (props) => {
    const [todos, dispatch] = useReducer(reducer, initialTodos);

    const handleComplete = (todo) => {
        dispatch({ type: "COMPLETE", id: todo.id });
    };

    return (
        <div>
            <div className='scroll-up-dashboard'>
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="/test">Test</a></li>
                </ul>
            </div>

            {todos.map((todo) => (
                <div key={todo.id}>
                    <label>
                        <input
                            type="checkbox"
                            checked={todo.complete}
                            onChange={() => handleComplete(todo)}
                        />
                        {todo.title} complete: {todo.complete ? 'true' : 'false'}
                    </label>
                </div>
            ))}
        </div>
    )
};

export default Test

useCallback Hook

The useCallback Hook chỉ chạy khi một trong các component con của nó được updated. Điều này có thể tăng performence.
useCallback is to prevent a component from re-rendering unless its props have changed
The useCallback and useMemo Hooks là giống nhau. Khác biệt là useMemo returns a memoized value còn useCallback returns a memoized function.
referential equality => Every time a component re-renders, its functions get recreated 
(Khi một trong các component bị thay đổi, các compnent khác liên quan tới ở function cũng bị thay đổi theo)
Vì vậy nếu thấy component cùng cấp bị thay đổi thì dùng useCallback để tránh bị ảnh hưởng hiệu suất.

link test: https://www.w3schools.com/react/showreact.asp?filename=demo2_react_usecallback2

# ở ví dụ dưới đây, useCallback hook sẽ giúp ngăn chặn component re-render không cần thiết
# bằng cách bỏ hàm setDodos vào trong useCallback function.

# index.js 
import { useState, useCallback } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  // original: sẽ render lại Todos khi button được click 
  /* const addTodo = () => {
        setTodos((t) => [...t, "New Todo"]);
  };*/

  const addTodo = useCallback(() => {            // giúp ngăn component Todos render lại không cần thiết
     setTodos((t) => [...t, "New Todo"]);
  }, [todos]);

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

# Todos.js
import { memo } from "react";

const Todos = ({ todos, addTodo }) => {           // truyền cả biến và hàm xuống component con
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
      <button onClick={addTodo}>Add Todo</button> // ở đây sự kiện được truyền xuống của parent component
    </>
  );
};

export default memo(Todos);

React Memo

Dùng memo có thể giữ cho React không re-render component nếu props của nó không bị thay đổi.
Ví dụ sau có thể trùng lặp với ví dụ trên.
# index.js
import { useState } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState(["todo 1", "todo 2"]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  return (
    <>
      <Todos todos={todos} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
# Todos.js
import { memo } from "react";

const Todos = ({ todos }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
    </>
  );
};

export default memo(Todos); 

Custom Hook:

link document: https://www.w3schools.com/react/react_customhooks.asp

<img class="img_ev3q" src="https://redux.js.org/assets/images/one-way-data-flow-04fe46332c1ccb3497ecb04b94e55b97.png" alt="One-way data flow" width="800" height="541" loading="lazy">

redux

Redux helps you manage "global" state - state that is needed across many parts of your application.

Redux is more useful when:      
    - You have large amounts of application state that are needed in many places in the app     
    - The app state is updated frequently over time     
    - The logic to update that state may be complex     
    - The app has a medium or large-sized codebase, and might be worked on by many people

The center of every Redux application is the store. A "store" is a container that holds your application's global state.

<img class="img_ev3q" src="https://d33wubrfki0l68.cloudfront.net/01cc198232551a7e180f4e9e327b5ab22d9d14e7/b33f4/assets/images/reduxdataflowdiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif" alt="Redux data flow diagram" width="800" height="600" loading="lazy">

Redux Store

Holds the current application state inside
Allows access to the current state via store.getState();
Allows state to be updated via store.dispatch(action);
Registers listener callbacks via store.subscribe(listener); // không dùng nhiều, vì useSelector đã hỗ trợ 
Handles unregistering of listeners via the unsubscribe function returned by store.subscribe(listener). // không dùng nhiều, vì useSelector đã hỗ trợ 
React-Redux hook: useSelector
    - which lets your React components read data from the Redux store.
    - useSelector automatically subscribes to the Redux store for us
    - All we have to do is call useSelector() once in our component, and it does the rest of the work for us
    - Lưu ý: useSelector sử dụng strict ===, nghĩa là component sẽ re-render mỗi khi state có tạo reference mới 
       vd: return state.todos.map(todo =&gt; todo.text) // tạo reference mới

   # dùng useSelector để lấy state ra khỏi global state
        import { useSelector } from 'react-redux' 

       const setTodo = state =&gt; state.todos;
       const todos = useSelector(setTodo)                // lấy state "todos" ra khỏi state global

       const todos = useSelector(state =&gt; state.todos) // cách khác để lấy state "todos"

# hàm cơ bản 
    store.getState(): lấy global state ra

# useDispatch hook: dispatch một action 
   You can think of dispatching actions as "triggering an event" in the application.

  import {useDispatch} from 'react-redux'
   const dispatch = useDispatch();

   dispatch({type:COUNTER_DECREMENT})             // dùng dispatch function để dispatch 
   store.dispatch({ type: 'counter/incremented' }) // dùng store để dispatch 

# action
   action là một object, ví dụ: { type: 'counter/incremented', payload: 10 }

# reducer: thay đổi trạng thái state (ở global)
const initialState = {value : 0}

function counterReducer(state = initialState, action) {
    const { type } = action
    switch (type) {
       case 'counter/incremented':
           return {
           ...state,
             value: state.value + 1,
       }

       case COUNTER_DECREMENT:
           return {
               ...state,
                value: state.value - 1,
           }
       default:
           return state
    }
}
export default counterReducer
Hầu hết Form không dùng redux để lưu trữ state. 
Thay vào đó, giữ data ở Form component bằng local state. Sau đó dispatch Redux action để update global state khi user thực hiện xong công việc.
Sau khi update global state, view sẽ được render lại. 
# có thể dùng shallowEqual để chặn các component con re-render khi một trong các component ngang hàng thay đổi
# hoặc có thể dùng memo để bao component con như bên react 
# ví dụ sau dùng shallowEqual 

import React from 'react'
import { useSelector, shallowEqual } from 'react-redux'
import TodoListItem from './TodoListItem'

const selectTodoIds = state =&gt; state.todos.map(todo =&gt; todo.id)

const TodoList = () =&gt; {
  const todoIds = useSelector(selectTodoIds, shallowEqual)

  const renderedListItems = todoIds.map(todoId =&gt; {
    return &lt;TodoListItem key={todoId} id={todoId} /&gt;
  })

  return &lt;ul className="todo-list"&gt;{renderedListItems}&lt;/ul&gt;
}

Example to use react redux:

# action/type.js

export const COUNTER_INCREMENT = "COUNTER_INCREMENT"
export const COUNTER_DECREMENT = "COUNTER_DECREMENT"
# reducer/counter.js

import { COUNTER_INCREMENT, COUNTER_DECREMENT } from '../actions/type'

const initialState = {value : 0}

function counterReducer(state = initialState, action) {
    const { type } = action
    switch (type) {
        case COUNTER_INCREMENT:
            return {
                ...state,
                value: state.value + 1,
            }

        case COUNTER_DECREMENT:
            return {
                ...state,
                value: state.value - 1,
            }
        default:
            return state
    }
}
export default counterReducer
# test/counter.js

import React from 'react'
import store from "../store";
import {COUNTER_INCREMENT, COUNTER_DECREMENT} from "../actions/type";
import {useDispatch, useSelector} from 'react-redux'


const Test = (props) =&gt; {
    const dispatch = useDispatch();
    const value = useSelector(state =&gt; state.counterReducer.value);

    return (
        &lt;div&gt;
            You clicked: {value} times
            &lt;button onClick={() =&gt; store.dispatch({type:COUNTER_INCREMENT})}&gt;+&lt;/button&gt;
            &lt;button onClick={() =&gt; dispatch({type:COUNTER_DECREMENT})}&gt;-&lt;/button&gt; // another way to dispatch
        &lt;/div&gt;
    )
};

export default Test

<img class="img_ev3q" src="https://redux.js.org/assets/images/ReduxAsyncDataFlowDiagram-d97ff38a0f4da0f327163170ccc13e80.gif" alt="Redux async data flow diagram" width="800" height="600" loading="lazy">

use thunk

npm install redux-thunk
# src/store.js

import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from "redux-devtools-extension"
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const initialState = {}
const middleware = [thunk]

const store = createStore(
  rootReducer,
  initialState,
  composeWithDevTools(applyMiddleware(...middleware))
)
export default store
// Thunk function
export async function fetchTodos(dispatch, getState) {
 const response = await client.get('/fakeApi/todos')
 dispatch({ type: 'todos/todosLoaded', payload: response.todos })
}

// Write a synchronous outer function that receives the `text` parameter:
export function saveNewTodo(text) {
  // And then creates and returns the async thunk function:
  return async function saveNewTodoThunk(dispatch, getState) {
    // ✅ Now we can use the text value and send it to the server
    const initialTodo = { text }
    const response = await client.post('/fakeApi/todos', { todo: initialTodo })
    dispatch({ type: 'todos/todoAdded', payload: response.todo })
  }
}

# usage

import { fetchTodos } from './features/todos/todosSlice'
store.dispatch(fetchTodos)

 if (e.which === 13 && trimmedText) {
 // Create the thunk function with the text the user wrote
 const saveNewTodoThunk = saveNewTodo(trimmedText)
 // Then dispatch the thunk function itself
 dispatch(saveNewTodoThunk) 

another example

# thunk here
export const updateScrollUp = (data) =&gt; async (dispatch) =&gt; {
    try {    
        const res = await ScrollUpService.update(data)     // make api call
        dispatch({
            type: UPDATE_SCROLL_UP,
            payload: data,
        })
        return Promise.resolve(res.data)
    } catch (error) {
        return Promise.reject(error)
    }
}

// call here
dispatch(updateScrollUp(data))
  .then(response =&gt; {
    LogService.debug('updateSetting.response', response)
    showSuccessMessage(CommonService.messages.settingUpdated)
  })
  .catch(e =&gt; {
    handleApiError(e)
  })
  .finally(() =&gt; {
    setUpdatingSetting(false)
  })