Là hàm quy định các render ra các JSX element hoặc component mà được truyền vào props của một component khác.

Kỹ thuật sử dụng render prop được gọi là render prop pattern.

Xét component sau:

// Amount.jsx
function Converter() {
  const [amount, setAmount] = useState(0)
 
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={() => setAmount(amount + 1)}>+</button>
      <button onClick={() => setAmount(amount - 1)}>-</button>
    </div>
  )
}

Component trên cho phép người dùng thay đổi lượng tiền cần quy đổi từ US dollar sang các tỷ giá khác.

Giả sử ta có hai loại tiền tệ cần quy đổi là Euro và Pound như sau:

// Currency.jsx
export function Euro({ amount }) {
  return <p>Euro: {amount * 0.86}</p>
}
 
export function Pound({ amount }) {
  return <p>Pound: {amount * 0.76}</p>
}

Làm thế nào để render ra hai component trên, với props truyền vào là lượng tiền cần quy đổi.

Approach 1: Just Render Them Within the Component

Chúng ta có thể render chúng trực tiếp ở bên trong component Converter như sau:

import { Euro, Pound } from "./Currency"
 
function Converter() {
  const [amount, setAmount] = useState(0)
 
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={() => setAmount(amount + 1)}>+</button>
      <button onClick={() => setAmount(amount - 1)}>-</button>
 
      <Euro amount={amount} />
      <Pound amount={amount} />
    </div>
  )
}

Điểm bất lợi của cách làm này là component Converter cần phải biết đến sự tồn tại của các child component (EuroPound). Trong tương lai, nếu có thêm nhiều tỷ giá khác thì ta cần phải import các child component và sửa lại component Converter.

Approach 2: Lifting State

Ta có thể đưa state của component Converter và các child component của nó lên parent component (App) như sau:

function App() {
  const [amount, setAmount] = useState(0)
 
  return (
    <>
      <Converter
        amount={amount}
        onIncrement={() => setAmount(amount + 1)}
        onDecrement={() => setAmount(amount - 1)}
      />
 
      <Euro amount={amount} />
      <Pound amount={amount} />
    </>
  )
}

Component Converter khi đó là:

function Converter({ amount, onIncrement, onDecrement }) {
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
      {children}
    </div>
  )
}

Đây là một cách giải quyết hợp lệ nhưng sẽ gây khó khăn trong việc mở rộng sau này, đặc biệt là khi có thêm nhiều component trung gian. Khi đó, ta cần phải truyền props nhiều lần hoặc dùng đến context.

Approach 3: Component Composition with Children Prop

Có thể truyền các child component vào thuộc tính children của props (xem thêm Passing JSX as Children):

function Converter({ amount, onIncrement, onDecrement, children }) {
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
      {children}
    </div>
  )
}

Tuy nhiên, tương tự như cách làm 2, ta cũng cần phải đưa state lên parent component của Converter:

function App() {
  const [amount, setAmount] = useState(0)
 
  return (
    <Converter
      amount={amount}
      onIncrement={() => setAmount(amount + 1)}
      onDecrement={() => setAmount(amount - 1)}
    >
      <Euro amount={amount} />
      <Pound amount={amount} />
    </Converter>
  )
}

Render Prop

Thay vì truyền vào các child component cần render (EuroPound), ta có thể truyền vào một callback giúp render ra hai component đó, với đối số là props mà ta muốn truyền vào các child component.

function App() {
  return (
    <Converter>
      {(amount) => {
        return (
          <>
            <Euro amount={amount} />
            <Pound amount={amount} />
          </>
        )
      }}
    </Converter>
  )
}

Ở trong component Converter, ta sẽ gọi thực thi children và truyền vào state amount:

function Converter({ children }) {
  const [amount, setAmount] = useState(0)
 
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={() => setAmount(amount + 1)}>+</button>
      <button onClick={() => setAmount(amount - 1)}>-</button>
      {children(amount)}
    </div>
  )
}

Cách làm này giải quyết được vấn đề về dependency (cách làm 1) và vấn đề về lifting state (cách làm 2 và 3).

Chúng ta cũng có thể truyền callback vào Converter thông qua một prop:

function App() {
  return (
    <Converter
      render={(amount) => {
        return (
          <>
            <Euro amount={amount} />
            <Pound amount={amount} />
          </>
        )
      }}
    ></Converter>
  )
}

Và gọi dùng prop đó như sau:

function Converter({ render }) {
  const [amount, setAmount] = useState(0)
 
  return (
    <div>
      <p>US Dollar: {amount} </p>
      <button onClick={() => setAmount(amount + 1)}>+</button>
      <button onClick={() => setAmount(amount - 1)}>-</button>
      {render(amount)}
    </div>
  )
}

Đó cũng là lý do mà ta gọi callback được truyền vào là render prop.

Resources