
Props Getters en React: Aprende a aplicar personalización y control de componentes 2025

Juan Correa
Desarrollador de Software Senior
El Props Getters pattern en React consiste en una manera de proveer un conjunto de props a los usuarios de tus componentes que van a necesitar en su implementación.
La manera en que esto funciona es proveyendo una función que devolverá los valores de estado, props, funciones o cualquier otros datos de tu componente o custom hook.
Esto nos ayuda a delegar una parte del control a los usuarios (quienes usen tu componente) sobre cómo se van a renderizar sus componentes.
Kent C. Dodds es un ingeniero de software que ha divulgado información sobre este patrón mayormente, por lo que le doy créditos ya que gracias a él pude conocer este patrón y ahora puedo transmitirlo a más personas en este libro.
Por último, lo más común es usar este patrón en conjunto de otros patrones como custom hook, render props y control props.
Table of Contents
Ventajas y desventajas de Props Getters
La principal ventaja de este patrón es:
- Provee un acceso a los props o valores internos de un componente o custom hook que hayas creado junto la posibilidad de extender o modificarlos (lo veremos en el ejemplo).
- Flexibilidad al usuario para personalizar sus componentes (lo veremos en el ejemplo).
La principal “desventaja” de este patrón es:
- Necesitas usarlo en conjunto de otro patrón para obtener los máximos beneficios. En mi experiencia, este patrón puede ser implementado sin costo de agregar complejidad extra.
¿Quieres dominar los Patrones de Diseño en React.js?
En mi ebook "Patrones avanzados en React.js", encontrarás más información sobre los patrones explicados en este post y contenido de alta calidad que te ayudará a llevar tus habilidades al siguiente nivel.
Descargar ebook gratisEn qué casos aplicar Props Getters
Hay principalmente tres casos de uso:
- Quieres proveer un acceso a los props de tu componente o valores internos de un custom hook de una manera centralizada.
- Quieres proveer una extensión en los valores internos de un custom hook con la opción de sobreescribir o extender dichos valores.
- Dar el control al usuario de utilizar los props de tu componente para que los aplique en su interfaz en la manera que desee (flexibilidad).
Ejemplo de Props Getters en vídeo
Si eres más de aprender visualmente o simplemente quieres un resumen rápido, no te pierdas este vídeo donde explico el patrón Control Props en React JS paso a paso:
Ejemplo de Props Getters
Para este ejemplo vamos a usar un componente existente que viene en la documentación de material-ui que sirve para hacer una lista de transferencia, es decir, un componente que permite al usuario mover uno o más elementos de lista entre listas.
Realizaremos un refactor a este componente de material ui para desacoplar la lógica de transferencia de elementos entre listas y proveerla de tal manera que se puedan implementar listas transferibles de manera independiente de la interfaz de usuario.
Esto nos va a permitir implementar componentes propios o de otras bibliotecas. Haremos dos variantes del patrón props getters:
- Creando un componente en conjunto con el patrón render props.
- Usando un custom hook.
Con el patrón props getters además permitiremos maximizar la customización de los props dejando posible la opción de extender las funcionalidades según lo que requiera el usuario del componente.
Opino que esto es lo que le da más valor a este patrón.
Al momento de escribir este post, el código del componente de material ui luce del siguiente modo. Prepárate que vas a ver un buen bloque de código.
function not(a, b) {
return a.filter((value) => b.indexOf(value) === -1)
}
function intersection(a, b) {
return a.filter((value) => b.indexOf(value) !== -1)
}
export default function TransferList() {
const classes = useStyles()
const [checked, setChecked] = React.useState([])
const [left, setLeft] = React.useState([0, 1, 2, 3])
const [right, setRight] = React.useState([4, 5, 6, 7])
const leftChecked = intersection(checked, left)
const rightChecked = intersection(checked, right)
const handleToggle = (value) => () => {
const currentIndex = checked.indexOf(value)
const newChecked = [...checked]
if (currentIndex === -1) {
newChecked.push(value)
} else {
newChecked.splice(currentIndex, 1)
}
setChecked(newChecked)
}
const handleAllRight = () => {
setRight(right.concat(left))
setLeft([])
}
const handleCheckedRight = () => {
setRight(right.concat(leftChecked))
setLeft(not(left, leftChecked))
setChecked(not(checked, leftChecked))
}
const handleCheckedLeft = () => {
setLeft(left.concat(rightChecked))
setRight(not(right, rightChecked))
setChecked(not(checked, rightChecked))
}
const handleAllLeft = () => {
setLeft(left.concat(right))
setRight([])
}
const customList = (items) => (
<Paper className={classes.paper}>
<List dense component="div" role="list">
{items.map((value) => {
const labelId = `transfer-list-item-${value}-label`
return (
<ListItem key={value} role="listitem" button onClick={handleToggle(value)}>
<ListItemIcon>
<Checkbox
checked={checked.indexOf(value) !== -1}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText
id={labelId}
primary={`List item
${value + 1}`}
/>
</ListItem>
)
})}
<ListItem />
</List>
</Paper>
)
return (
<Grid container spacing={2} justify="center" alignItems="center" className={classes.root}>
<Grid item>{customList(left)}</Grid>
<Grid item>
<Grid container direction="column" alignItems="center">
<Button
variant="outlined"
size="small"
className={classes.button}
onClick={handleAllRight}
disabled={left.length === 0}
aria-label="move all right"
>
≫
</Button>
<Button
variant="outlined"
size="small"
className={classes.button}
onClick={handleCheckedRight}
disabled={leftChecked.length === 0}
aria-label="move selected right"
>
>
</Button>
<Button
variant="outlined"
size="small"
className={classes.button}
onClick={handleCheckedLeft}
disabled={rightChecked.length === 0}
aria-label="move selected left"
>
<
</Button>
<Button
variant="outlined"
size="small"
className={classes.button}
onClick={handleAllLeft}
disabled={right.length === 0}
aria-label="move all left"
>
≪
</Button>
</Grid>
</Grid>
<Grid item>{customList(right)}</Grid>
</Grid>
)
}
Vaya, esto sí es un buen tamaño de código.
Lo importante a destacar de este ejemplo es la lógica para implementar la transferencia de elementos entre listas.
Se usan arrays como estructura de datos de tal modo que la lógica para mover elementos entre listas se reduce a operaciones de filtrado, concatenación y actualización de dichos arrays.
Una vez explicado lo anterior, procedemos a crear nuestro componente TransferList.
const TransferList = ({ left = [], right = [], children }) => {
const [checkedElements, setCheckedElements] = useState([])
const [leftList, setLeftList] = useState([...left])
const [rightList, setRightList] = useState([...right])
const leftChecked = intersection(checkedElements, leftList)
const rightChecked = intersection(checkedElements, rightList)
const handleToggle = (event) => {
const { name } = event.target
const currentIndex = checkedElements.indexOf(name)
const newChecked = [...checkedElements]
if (currentIndex === -1) {
newChecked.push(name)
} else {
newChecked.splice(currentIndex, 1)
}
setCheckedElements(newChecked)
}
const handleAllRight = () => {
setRightList(rightList.concat(leftList))
setLeftList([])
}
const handleAllLeft = () => {
setLeftList(leftList.concat(rightList))
setRightList([])
}
const handlePassCheckedToLeft = () => {
setLeftList(leftList.concat(rightChecked))
91
setRightList(not(rightList, rightChecked))
setCheckedElements(not(checkedElements, rightChecked))
}
const handlePassCheckedToRight = () => {
setRightList(rightList.concat(leftChecked))
setLeftList(not(leftList, leftChecked))
setCheckedElements(not(checkedElements, leftChecked))
}
const getTransferListProps = () => ({
handleAllRight,
handleAllLeft,
handlePassCheckedToLeft,
handlePassCheckedToRight,
})
const getToogleProps = ({ name, ...rest }) => ({
onChange: handleToggle,
type: 'checkbox',
name,
...rest,
})
return children({
leftList,
rightList,
getTransferListProps,
getToogleProps,
})
}
Este componente es básicamente un copy - paste del original salvo las siguientes diferencias:
- Hemos eliminado toda lógica de UI.
- Estamos aplicando render props al ejecutar el prop children como función.
- Existen dos nuevas funciones que son la esencia de los props getters:
getTransferListPropsygetToogleProps.
Las funciones getTransferListProps y getToogleProps regresan un objeto con los valores correspondientes a cada una:
getTransferListProps: retorna las funciones con la lógica necesaria para aplicar los cambios entre listas.getToogleProps: retorna las propiedades mínimas necesarias para identificar los elementos de listas en la interfaz de usuario.
A continuación un ejemplo de uso de este componente:
<TransferList left={['Value 1', 'Value 2']} right={['Value 3', 'Value 4']}>
{({ leftList, rightList, getToogleProps, getTransferListProps }) => {
/* content to render here */
}}
</TransferList>
Este es el patrón render props en acción. El elemento children es una función que recibe los parámetros enviados por TransferList.
Ahora veamos un ejemplo de implementación completa para poder apreciar la customización de la interfaz de usuario.
export default function App() {
const renderList = ({ list, getToggleProps }) => {
return (
<ul>
{list.map((name) => (
<li key={name}>
<label>
<input {...getToggleProps({ name })} />
{name}
</label>
</li>
))}
</ul>
)
}
return (
<div className="App">
<h1>Patrón props getters de React</h1>
<TransferList left={['Value 1', 'Value 2']} right={['Value 3', 'Value 4']}>
{({ leftList, rightList, getToggleProps, getTransferListProps }) => {
const {
handleAllRight,
handleAllLeft,
handlePassCheckedToLeft,
handlePassCheckedToRight,
} = getTransferListProps()
return (
<main className="list-container">
<header className="list-header">
<h2>Mi lista de transferencia</h2>
</header>
<section className="list-values">
{renderList({ list: leftList, getToggleProps })}
</section>
<section className="list-buttons">
<button onClick={handleAllRight}>≫</button>
<button onClick={handlePassCheckedToRight}>></button>
<button onClick={handlePassCheckedToLeft}><</button>
<button onClick={handleAllLeft}>≪</button>
</section>
<section className="list-values">
{renderList({ list: rightList, getToggleProps })}
</section>
</main>
)
}}
</TransferList>
</div>
)
}
Este código lo puedes ejecutar en este enlace: https://codesandbox.io/s/patron-prop-getters-con-render-props-en-react-vz9sm
Lo importante a destacar del código anterior es el uso de las funciones que aplican el patrón prop getters.
<input {...getToogleProps({ name })} />
Recordemos que getToogleProps retorna un objeto con propiedades:
const getToogleProps = ({ name, ...rest }) => ({
onChange: handleToggle,
type: 'checkbox',
name,
...rest,
})
Todas estas propiedades son pasadas al elemento input haciendo una destructuración.
Si el usuario llegara a necesitar más valores o sobreescribir los que recibe, podemos hacerlo sin problema pasándolos por parámetro.
<input {...getToogleProps({ name, disabled: true, id: 'myId' })} />
Es en este caso de uso donde comenzamos a ver el valor de los props getters.
Lo mismo aplica para getTransferListProps. A continuación coloco las partes del código que son relevantes.
const { handleAllRight, handleAllLeft, handlePassCheckedToLeft, handlePassCheckedToRight } =
getTransferListProps()
/* Resto del código */
;<section className="list-buttons">
<button onClick={handleAllRight}>≫</button>
<button onClick={handlePassCheckedToRight}>></button>{' '}
<button onClick={handlePassCheckedToLeft}><</button> <button onClick={handleAllLeft}>≪</button>
</section>
¿Pero qué pasa si el usuario necesita extender los eventos click ejecutando extendiendo las funciones handlers o manejadoras de eventos?
Vamos a hacer un cambio extra en nuestro componente TransferList para permitir al usuario extender las funcionalidades.
Basados en el brillante trabajo de Kent Dodds, usaremos esta función de utilidad para ejecutar funciones de manera dinámica.
const callAll =
(...fns) =>
(...args) =>
fns.forEach((fn) => fn && fn(...args))
Es solo una función que recibe por parámetro N cantidad de funciones. Al usar la desstructuración, fns va a ser igual a un array que contenga todas las funciones que vienen como parámetros.
Cada una de las potenciales funciones a recibir, al ser funciones, reciben sus propios parámetros. La segunda función que contiene ...args cumple el papel de proveerlos.
Al final se iteran todas las funciones y se ejecutan con sus parámetros correspondientes. Ahora vamos a aplicarla en la función getToogleProps:
const getToogleProps = (props = {}) => ({
type: 'checkbox',
name: props.name,
...props,
onChange: callAll(props.onChange, handleToggle),
})
Como podemos ver, ya no destructuramos el objeto recibido por parámetro. Ahora le asignamos un valor de objeto vacío como default.
Al final del objeto que retornamos, colocamos la propiedad onChange. Esto es importante debido a que si lo ponemos antes que ...props, se va a sobreescribir.
En la propiedad onChange estamos ejecutando la función callAll, pasando primero el onChange de los props y luego handleToggle, que es la función del componente.
Vamos a probar este cambio haciendo un alert cada vez que damos click a un elemento de un listado.
const notify = () => alert('clicked!')
const renderList = ({ list, getToogleProps }) => {
return (
<ul>
{list.map((name) => (
<li key={name}>
<label>
<input
{...getToogleProps({
name,
onChange: notify,
})}
/>
{name}
</label>
</li>
))}
</ul>
)
}
Con esto ahora sí que estamos aprovechando el patrón props getters.
Para finalizar, vamos a hacer la versión de este patrón pero con un custom hook.
Usando Props Getters con un Custom Hook
const useTransferList = ({ left = [], right = [] }) => {
const [checkedElements, setCheckedElements] = useState([])
const [leftList, setLeftList] = useState([...left])
const [rightList, setRightList] = useState([...right])
const leftChecked = intersection(checkedElements, leftList)
const rightChecked = intersection(checkedElements, rightList)
const handleToggle = (event) => {
/* code */
}
const handleAllRight = () => {
/* code */
}
const handleAllLeft = () => {
/* code */
}
const handlePassCheckedToLeft = () => {
/* code */
}
const handlePassCheckedToRight = () => {
/* code */
}
const getTransferListProps = () => ({
handleAllRight,
handleAllLeft,
handlePassCheckedToLeft,
handlePassCheckedToRight,
})
const getToogleProps = (props = {}) => ({
type: 'checkbox',
name: props.name,
...props,
onChange: callAll(props.onChange, handleToggle),
})
return {
leftList,
rightList,
getTransferListProps,
getToogleProps,
}
}
Es prácticamente un copy - paste del componente TransferList, pero retornamos las propiedades con el hook.
Así es como lo podemos usar:
export default function App() {
const { leftList, rightList, getTransferListProps, getToogleProps } = useTransferList({
left: ['Value 1', 'Value 2'],
right: ['Value 3', 'Value 4'],
})
const { handleAllRight, handleAllLeft, handlePassCheckedToLeft, handlePassCheckedToRight } =
getTransferListProps()
const renderList = ({ list, getToogleProps }) => {
// code
}
return (
<div className="App">
<h1>Patrón props getters de React</h1>
<main className="list-container">
<header className="list-header">
<h2>Mi lista de transferencia</h2>
</header>
<section className="list-values">{renderList({ list: leftList, getToogleProps })}</section>
<section className="list-buttons">
<button onClick={handleAllRight}>99 ≫</button>
<button onClick={handlePassCheckedToRight}>></button>
<button onClick={handlePassCheckedToLeft}><</button>
<button onClick={handleAllLeft}>≪</button>
</section>
<section className="list-values">{renderList({ list: rightList, getToogleProps })}</section>
</main>
</div>
)
}
Es más limpio cuando lo aplicamos usando el custom hook que con los render props, por lo que personalmente prefiero combinar los props getters con los custom hooks.
Conclusión sobre Props Getters
Probablemente el patrín Props Getters sea uno de los patrones que parecen complejos en un inicio pero una vez que los comprendes, te das cuenta que su uso es realmente sencillo de hacer.
¿Quieres aprender más sobre patrones de diseño en React JS?
Si te ha gustado este análisis sobre el patrón Props Getters, te invito a explorar los otros patrones avanzados en React JS que he analizado en la guía principal.
Y recuerda, si quieres llevar tus habilidades al siguiente nivel, no te pierdas el curso completo en el Canal de YouTube de Developero.