Skip to content

Commit 62a6bf0

Browse files
Merge pull request #158 from saulin18/patch-1
a simple reactjs article for cucoders community
2 parents 1d64c62 + 3c2f688 commit 62a6bf0

File tree

1 file changed

+321
-0
lines changed

1 file changed

+321
-0
lines changed
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
---
2+
title: "Estás usando mal React"
3+
pubDate: "Sun Jun 15 2025"
4+
image: "https://th.bing.com/th/id/OIP.SSmzcDoacILZm1PrG7_DHwHaD4?rs=1&pid=ImgDetMain&cb=idpwebpc1"
5+
username: "saulin18"
6+
categories: ["tutorials","software","web"]
7+
description: "Este tutorial se enfoca en cómo construir componentes de React robustos, extensibles y reutilizables, todo explicado con ejemplos prácticos. "
8+
canonicalUrl: ""
9+
---
10+
11+
A lo largo de mis años en el desarrollo web con React he visto muchas veces que tienes que volver atrás a modificar componentes (o incluso rehacerlos desde 0) porque desde el inicio no se diseñó bien el componente, esto está **mal**.
12+
13+
Imagina que necesitas un componente button:
14+
15+
// Button.tsx
16+
import React from 'react';
17+
18+
interface ButtonProps {
19+
onClick: () => void;
20+
text: string;
21+
}
22+
23+
const Button: React.FC<ButtonProps> = ({ onClick, text }) => {
24+
return (
25+
<button onClick={onClick}>
26+
{text}
27+
</button>
28+
);
29+
};
30+
31+
export default Button;
32+
33+
<Button onClick={() => alert('Botón clickeado')} text="Click aquí" />
34+
35+
Este componente en primera instancia puede parecer que cumple para lo que fue asignado, pero a medida que crece el proyecto hay una gran posibilidad de que tengas que hacerle muchos cambios por no hacerlo más abierto a la modificación.
36+
37+
**Problemas iniciales:**
38+
39+
- onClick limitado: Si quisieras acceder al evento nativo del DOM (como event.preventDefault()), no podrías porque lo tipamos sin el evento.
40+
41+
- prop text: Solo acepta texto plano (string). Si en el futuro quisieras pasarle un ícono, una imagen, o cualquier JSX, tendrías que volver y modificar el componente
42+
43+
**¿Cómo mejorar este componente?**
44+
45+
Sencillamente tipando correctamente el evento de onClick y usando la prop children en lugar de pasarle el text como un string por props. Así no cierras el contenido que le puedes pasar al button haciéndolo más flexible.
46+
47+
// Button.tsx
48+
import React, { MouseEvent, ReactNode } from 'react';
49+
50+
interface ButtonProps {
51+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
52+
children: ReactNode;
53+
}
54+
55+
const Button: React.FC<ButtonProps> = ({ onClick, children }) => {
56+
return (
57+
<button onClick={onClick}>
58+
{children}
59+
</button>
60+
);
61+
};
62+
63+
export default Button;
64+
65+
// Usos:
66+
// <Button onClick={() => alert('¡Hola!')}>Click aquí</Button>
67+
// <Button onClick={() => alert('Icono!')}><img src="icon.png" alt="icono" /></Button>
68+
// <Button onClick={() => alert('Div!')}><div>Contenido</div></Button>
69+
70+
## ComponentPropsWithRef, ComponentPropsWithoutRef, ComponentPropsWithChildren, la clave para hacer componentes reutilizables.
71+
72+
Incluso con los cambios anteriores, ese componente sigue sin ser del todo reutilizable, aún mantiene implementaciones en su estructura. Si quisieras añadir una nueva funcionalidad al botón, como cambiar su type (a submit, reset, etc.) o añadirle un handler para un evento, tendrías que ir al componente Button y añadir esas props una por una.
73+
74+
Aquí es donde React nos da una joya: ComponentPropsWithRef (o ComponentProps si no necesitas pasar referencias). Este tipo te permite heredar todas las propiedades nativas de un elemento HTML. Así tu componente de automáticamente aceptará type, onBlur, className, id, style, y todo lo que un componente HTML nativo pueda tener.
75+
76+
El truco es hacer **spread** de esas props al elemento HTML del que se extiende. Esto debería hacerse en todos tus componentes que envuelvan elementos HTML primitivos.
77+
78+
// Button.tsx
79+
import React, { MouseEvent, ReactNode, ComponentPropsWithRef } from 'react';
80+
81+
// Definimos los props de nuestro botón usando ComponentPropsWithRef
82+
// Esto trae TODAS las props nativas de un <button> HTML
83+
84+
interface ButtonProps extends React.ComponentPropsWithRef<'button'> {
85+
// Props personalizadas aquí
86+
}
87+
88+
const Button: React.FC<ButtonProps> = ({ children, ...props }) => {
89+
// 'children' lo extraemos si lo vamos a usar directamente,
90+
// ComponentPropsWithRef ya incluye 'children', así que podríamos dejarlo en 'props' también.
91+
92+
93+
return (
94+
<button {...props}> {/* Spread a las props */}
95+
{children}
96+
</button>
97+
);
98+
};
99+
100+
export default Button;
101+
102+
// Usos:
103+
// <Button type="submit" onClick={(e) => e.preventDefault()}>Enviar Formulario</Button>
104+
// <Button onBlur={() => console.log('Salió del botón')} className="bg-blue-500 text-white p-2">Foco</Button>
105+
// <Button aria-label="Cerrar ventana">X</Button>
106+
107+
Ahora nuestro componente recibe autocompletado, es cómodo para extender y está abierto a la extensión. Este mismo truco del ComponentPropsWithRef, ComponentPropsWithoutRef, ComponentPropsWithChildren, etc, lo pueden usar en cada elemento que envuelva un elemento HTML primitivo.
108+
109+
// Ejemplo con Input
110+
import React, { ComponentPropsWithRef } from 'react';
111+
112+
interface InputProps extends React.ComponentPropsWithRef<'input'> {
113+
//Props adicionales ...
114+
}
115+
116+
const Input: React.FC<InputProps> = ({ ...props }) => {
117+
return <input {...props} />;
118+
};
119+
120+
export default Input;
121+
122+
// Uso:
123+
// <Input type="text" placeholder="Tu nombre" onChange={(e) => console.log(e.target.value)} />
124+
// <Input type="email" required />
125+
126+
**Lógica Personalizada y Cómo Evitar Sobreescrituras**
127+
128+
A veces querrás añadir tu propia lógica a las props nativas. Por ejemplo, podrías querer registrar un evento "X" cada vez que se hace clic en un botón además de la función onClick que el usuario pueda pasar.
129+
130+
El orden de esparcir las props importa. Si tienes una prop personalizada y una prop nativa con el mismo nombre, y esparces las props después de definir tu lógica personalizada, tu lógica personalizada será sobrescrita.***
131+
132+
**Esparce las props antes de tus propiedades personalizadas.**
133+
134+
Ejemplo:
135+
136+
// Button.tsx
137+
import React, { MouseEvent, ReactNode, ComponentPropsWithRef } from 'react';
138+
139+
interface ButtonProps extends ComponentPropsWithRef<'button'>{
140+
//Props personalizadas
141+
}
142+
143+
const Button: React.FC<ButtonProps> = ({ children, onClick, ...props }) => {
144+
const handleInternalClick = (event: MouseEvent<HTMLButtonElement>) => {
145+
// 1. Lógica personalizada: Enviamos un informe de análisis
146+
console.log('Enviando informe de análisis por clic del botón...');
147+
148+
// 2. Llamamos al onClick original que el usuario pudo haber pasado
149+
if (onClick) {
150+
onClick(event);
151+
}
152+
};
153+
154+
return (
155+
<button
156+
{...props} // ¡Primero esparcimos todas las props!
157+
onClick={handleInternalClick} // Luego definimos nuestro onClick personalizado
158+
>
159+
{children}
160+
</button>
161+
);
162+
};
163+
164+
export default Button;
165+
166+
// Uso:
167+
// <Button onClick={() => alert('El usuario hizo click!')}>Mi Botón</Button>
168+
// Aquí, la función 'handleInternalClick' se ejecutará primero, luego 'alert'
169+
170+
Ejemplo con el className:
171+
172+
// Button.tsx
173+
import React, { ReactNode, ComponentPropsWithRef } from 'react';
174+
175+
interface ButtonProps extends ComponentPropsWithRef<'button'>{
176+
//Props personalizadas ...
177+
}
178+
179+
180+
const Button: React.FC<ButtonProps> = ({ children, className, ...props }) => {
181+
// Clases CSS base para tu botón
182+
const baseClasses = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";
183+
184+
return (
185+
<button
186+
{...props} // Spread a las props
187+
className={`${baseClasses} ${className || ''}`} // Luego combinamos nuestras clases con las del usuario
188+
>
189+
{children}
190+
</button>
191+
);
192+
};
193+
194+
export default Button;
195+
196+
// Uso:
197+
// <Button className="w-full mt-4">Guardar</Button>
198+
// El botón tendrá las clases base Y "w-full mt-4"
199+
200+
**Props por defecto y el uso de props opcionales.**
201+
202+
Muchos elementos HTML tienen comportamientos por defecto. Esto puede causar resultados inesperados.
203+
Es una buena práctica definir valores por defecto sensatos para tus props. Esto hace que tu componente sea más predecible y seguro.
204+
205+
Cuando construyas componentes, intenta hacer que la mayoría de tus props sean opcionales. Si añades una nueva prop y la haces obligatoria, romperás todas las instancias de tu componente que ya existan en tu aplicación.
206+
Si una prop es necesaria para una nueva característica, hay dos maneras de manejarlo cuando es opcional:
207+
208+
- Definir un valor por defecto: Si no se pasa la prop, tendrá un valor predeterminado.
209+
210+
- Manejar undefined: Si la prop no se pasa, la lógica de tu componente debe ser capaz de funcionar sin ella.
211+
212+
import React from 'react';
213+
214+
interface ButtonProps extends React.ComponentPropsWithRef<'button'> {
215+
// Props opcionales con valores por defecto
216+
variant?: 'primary' | 'secondary' | 'danger';
217+
size?: 'small' | 'medium' | 'large';
218+
219+
// Props opcionales que se manejan con undefined
220+
icon?: React.ReactNode;
221+
loadingText?: string;
222+
}
223+
224+
const Button: React.FC<ButtonProps> = ({
225+
children,
226+
variant = 'primary',
227+
size = 'medium',
228+
icon,
229+
loadingText,
230+
disabled,
231+
...props
232+
}) => {
233+
const isLoading = disabled;
234+
235+
return (
236+
<button
237+
{...props}
238+
disabled={disabled}
239+
className={btn btn-${variant} btn-${size}}
240+
>
241+
242+
{icon && <span className="icon">{icon}</span>}
243+
244+
{isLoading ? (loadingText || 'Cargando...') : children}
245+
</button>
246+
);
247+
};
248+
249+
**Composition**
250+
251+
Todos los componentes que hemos visto han sido simples, cascarones, base, pero no siempre tenemos componentes así. Muchas veces tenemos componentes que componen por ejemplo un Button, un Input y en el mismo componente manejamos el estado de error (la clave es identificar los elementos relacionados de la UI). Aquí es donde entra la Composition. Podemos hacer un componente que haga spread y extienda de varios componentes HTML para así hacerlo aún más reutilizable.
252+
253+
// InputField.tsx
254+
import React, { ComponentPropsWithRef, ReactNode } from 'react';
255+
256+
// Props para el Label
257+
interface LabelProps extends Omit<ComponentPropsWithRef<'label'>, 'htmlFor'> {
258+
children: ReactNode;
259+
}
260+
261+
// Props para el mensaje de Error
262+
interface ErrorFieldProps extends ComponentPropsWithRef<'p'> {
263+
// Props adicionales ...
264+
}
265+
266+
// Props del Input principal
267+
interface InputFieldProps extends ComponentPropsWithRef<'input'> {
268+
name: string;
269+
labelProps?: LabelProps;
270+
errorProps?: ErrorFieldProps;
271+
buttonProps?: ComponentPropsWithRef<'button'>;
272+
}
273+
274+
const InputField: React.FC<InputFieldProps> = ({
275+
name,
276+
labelProps,
277+
errorProps,
278+
buttonProps,
279+
...inputProps
280+
}) => {
281+
const inputId = `input-${name}`;
282+
283+
return (
284+
<div className="flex flex-col gap-1">
285+
{labelProps && (
286+
<label htmlFor={inputId} {...labelProps} className={`font-semibold ${labelProps.className || ''}`}>
287+
{labelProps.children}
288+
</label>
289+
)}
290+
291+
<div className="flex items-center border rounded">
292+
<input
293+
id={inputId}
294+
name={name}
295+
{...inputProps}
296+
className={`flex-grow p-2 focus:outline-none ${inputProps.className || ''}`}
297+
/>
298+
{buttonProps && (
299+
<button
300+
{...buttonProps}
301+
className={`p-2 bg-gray-200 hover:bg-gray-300 ${buttonProps.className || ''}`}
302+
>
303+
{buttonProps.children || 'Action'}
304+
</button>
305+
)}
306+
</div>
307+
308+
{errorProps && (
309+
<p {...errorProps} className={`text-red-500 text-sm ${errorProps.className || ''}`}>
310+
{errorProps.children}
311+
</p>
312+
)}
313+
</div>
314+
);
315+
};
316+
317+
export default InputField;
318+
319+
Este enfoque, donde construyes componentes robustos y flexibles que se basan en los elementos HTML nativos y se pueden extender fácilmente, es una forma excelente de trabajar en React. Verás que muchas librerías de componentes modernas, como Shadcn, adoptan principios muy similares.
320+
321+
PD: El uso de Typescript no se aborda en este artículo por no ser una introducción a React, sino más bien a buenas prácticas. Si no estás usando Typescript **por favor**, empieza a hacerlo.

0 commit comments

Comments
 (0)