00:00 / 00:00
React + Typescript: tipando funcion
Pasar propiedades del tipo cadena de texto o un objeto está chupado con React, pero... ¿Y si queremos pasar una propiedad de tipo función? Esto es muy normal si por ejemplo queremos que un componente padre pueda capturar el evento onChange o el onClick de un componente hijo.
Manos a la obra
En vez de utilizar constantes para el nombre y apellidos, vamos a guardarlo como parte del estado:
export default function App() {
+ const [nombre, setNombre] = React.useState('Jose');
+ const [apellidos, setApellidos] = React.useState('García Pérez')
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MuestraNombre
<MuestraNombre
- nombre="Jose"
+ nombre={nombre}
- apellidos="García Pérez"
+ apellidos={apellidos}
/>
</div>
);
}
Vamos a empezar por añadir un botón a nuestro componente MuestraNombre que al clickarlo permita al padre realizar una operación, en este caso lo que haremos será borrar el campo apellido.
Lo primero que se nos puede ocurrir para implementar ésto es usar el tipo any, esto es un cajón de sastre y debemos de evitarlo en la medida de lo posible:
interface Props {
nombre: string;
apellidos: string;
+ onResetApellidos : any;
}
const MuestraNombre: React.FC<Props> = (props) => {
return (
+ <>
<h1>
{props.nombre} {props.apellidos}
</h1>
+ <button onClick={props.onResetApellidos}>Borrar apellidos</button>
+ </>
);
};
En el padre podemos consumirlo:
export default function App() {
const [nombre, setNombre] = React.useState('Jose');
const [apellidos, setApellidos] = React.useState('García Pérez')
+ const handleResetApellidos = () => {
+ setApellidos('');
+ }
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MuestraNombre
nombre={nombre}
apellidos={apellidos}
+ onResetApellidos={handleResetApellidos}
/>
</div>
);
}
Esto de usar any es un hack sucio, de hecho una medida para ver la calidad de código TypeScript, es ver cuantos any se están usando en el proyecto, ¿Qué limitaciones tengo aquí?
- Le puedo pasar cualquier valor, con lo que podría equivocarme y tener un error de ejecución.
- No conozco que firma tiene el callback que estoy definiendo, tendría que ponerme a mirar el código del componente para averiguarlo.
Si nos ponemos encima del onClick del button que hemos definido en MuestraNombre, podemos ver la firma que espera. En nuestro caso no vamos a hacer uso del parámetro que nos informa ese evento, sólo queremos saber cuándo se dispara el click del botón, lo que hacemos, es definir el tipo para onResetApellidos de la siguiente manera:
interface Props {
nombre: string;
apellidos: string;
- onResetApellidos: any;
+ onResetApellidos: () => void;
}
Aquí le estamos diciendo que onResetApellidos es de tipo función, que no recibirá parámetros y no se espera que devuelva nada (es de tipo void), si nos vamos ahora al componente App e intentamos cambiar el valor de la propiedad onResetApellidos por algo que no sea una función veremos que el editor nos resalta un error.
Para terminar, vamos a ver como manejarnos con el evento de un campo de tipo input. En este caso, sí que tenemos un valor que queremos pasar para arriba, lo que se esté tecleando en ese momento.
Vamos a reemplazar el campo texto que muestra los apellidos por un input
const MuestraNombre: React.FC<Props> = (props) => {
return (
<>
<h1>
- {props.nombre} {props.apellidos}
+ {props.nombre}
</h1>
+ <input value={props.apellidos}/>
<button onClick={props.onResetApellidos}>Borrar Apellidos</button>
</>
);
};
Si queremos modificar el campo apellidos tenemos que enviar la nueva propuesta de valor al componente padre y que éste haga el setState de turno.
Una opción para implementar ésto, sería ponernos encima del evento onChange del input y copiar la firma del mismo para añadirlo en las propiedades:
interface Props {
nombre: string;
apellidos: string;
onResetApellidos: () => void;
+ onSetApellidos: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const MuestraNombre: React.FC<Props> = (props) => {
return (
<>
<h1>{props.nombre}</h1>
<input
value={props.apellidos}
+ onChange={props.onSetApellidos}
/>
<button onClick={props.onResetApellidos}>Borrar Apellidos</button>
</>
);
};
Y vamos a consumirlo en App:
export default function App() {
const [nombre, setNombre] = React.useState("Jose");
const [apellidos, setApellidos] = React.useState("García Pérez");
const handleResetApellidos = () => {
setApellidos("");
};
+ const handleSetApellidos = (event: React.ChangeEvent<HTMLInputElement>) => {
+ setApellidos(event.target.value)
+ }
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MuestraNombre
nombre={nombre}
apellidos={apellidos}
onResetApellidos={handleResetApellidos}
+ onSetApellidos={handleSetApellidos}
/>
</div>
);
}
Esta aproximación en muchos casos no es óptima, ya que le estamos exponiendo al
componente padre el detalle de implementación de nuestro componente, le forzamos
saber qué es eso de event.target.value
, ¿Y si en un futuro decidiéramos no usar
un input? Lo ideal es abstraer el contrato, y usar algo común para los dos
componentes, en este caso el tipo string, veamos cómo:
interface Props {
nombre: string;
apellidos: string;
onResetApellidos: () => void;
- onSetApellidos: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ onSetApellidos: (value : string) => void;
}
const MuestraNombre: React.FC<Props> = (props) => {
return (
<>
<h1>{props.nombre}</h1>
- <input value={props.apellidos} onChange={props.onSetApellidos} />
+ <input value={props.apellidos} onChange={e => props.onSetApellidos(e.target.value)} />
<button onClick={props.onResetApellidos}>Borrar Apellidos</button>
</>
);
};
Y en el componente padre:
export default function App() {
const [nombre, setNombre] = React.useState("Jose");
const [apellidos, setApellidos] = React.useState("García Pérez");
const handleResetApellidos = () => {
setApellidos("");
};
- const handleSetApellidos = (event: React.ChangeEvent<HTMLInputElement>) => {
- setApellidos(event.target.value);
- };
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MuestraNombre
nombre={nombre}
apellidos={apellidos}
onResetApellidos={handleResetApellidos}
- onSetApellidos={handleSetApellidos}
+ onSetApellidos={setApellidos}
/>
</div>
);
}
¿Te apuntas a nuestro máster?
Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.