<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Desko | Desarrollo Frontend]]></title><description><![CDATA[I enjoy coding and ramen]]></description><link>https://desko.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 18:26:03 GMT</lastBuildDate><atom:link href="https://desko.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[¿Aprovechas TypeScript con i18next?]]></title><description><![CDATA[Si usas el maravilloso i18next harás esto normalmente:
<button>{t('OPEN')}</button>

Y en alguna otra parte tendrás un JSON con la traducción asociada a la clave OPEN, por ejemplo en español:
{
  "OPEN": "Abrir"
}

Esto es maravilloso, pero ¿y si esc...]]></description><link>https://desko.dev/i18next-typescript</link><guid isPermaLink="true">https://desko.dev/i18next-typescript</guid><dc:creator><![CDATA[Ismael Ramon]]></dc:creator><pubDate>Mon, 20 May 2024 23:07:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716245737785/0f131adc-18fc-44f4-9e37-828dbdd40505.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Si usas <a target="_blank" href="https://www.i18next.com/">el maravilloso i18next</a> harás esto normalmente:</p>
<pre><code class="lang-typescript">&lt;button&gt;{t(<span class="hljs-string">'OPEN'</span>)}&lt;/button&gt;
</code></pre>
<p>Y en alguna otra parte tendrás un JSON con la traducción asociada a la clave <code>OPEN</code>, por ejemplo en español:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"OPEN"</span>: <span class="hljs-string">"Abrir"</span>
}
</code></pre>
<p>Esto es maravilloso, pero ¿y si escribo mal la clave? 🤔</p>
<pre><code class="lang-typescript">&lt;button&gt;{t(<span class="hljs-string">'OPEM'</span>)}&lt;/button&gt;
</code></pre>
<p>Lógicamente i18next <a target="_blank" href="https://github.com/i18next/i18next/tree/master/typescript">viene con declaraciones TypeScript</a> y debería avisarme de que la clave no existe...</p>
<p>... ¿verdad?...</p>
<p>... ¿¡VERDAD!?</p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExYzhxZzhlMmh1cHBlbTh3Z2tjM3FjOWtpMjN2eGhzdWEyaXNlcjc1MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l4Jz9evyiEfPEwGis/giphy.gif" alt class="image--center mx-auto" /></p>
<p>Spoiler: no 🙃</p>
<h2 id="heading-cual-es-el-problema">Cuál es el problema</h2>
<p>El problema es que el sistema de tipos no ve tus JSON, que contienen las claves existentes.</p>
<p>Para TypeScript, la función <code>t</code> simplemente acepta un parámetro de tipo <code>string</code>, algo así:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> TFunc = <span class="hljs-function">(<span class="hljs-params">key: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">string</span>
</code></pre>
<p>Y por eso <code>t('OPEN')</code> es tan válido como <code>t('pepe')</code>, porque ambos valores son <code>string</code> y la función acepta <code>string</code>. La build pasa ✅ 🤦</p>
<p>Pero claro, si la clave no existe en el JSON al correr la app... no habrá traducción disponible y veremos <code>pepe</code> como placeholder 😱</p>
<p>¡PAM! 💥 Otro nuevo bug al backlog 🐛</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmJoZWs4cXk2djgwd2pzaHA3cm9weXg0enlwdzd4M3ozb29oNzBsYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Yjvg6BHVXJVOxwrG43/giphy.gif" alt class="image--center mx-auto" /></p>
<p>Let's fix this forever.</p>
<h2 id="heading-funcion-t-a-prueba-de-balas">Función <code>t</code> a prueba de balas</h2>
<p>Considerando que tenemos este JSON de traducciones al español:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"OPEN"</span>: <span class="hljs-string">"Abrir"</span>,
  <span class="hljs-attr">"CLOSE"</span>: <span class="hljs-string">"Cerrar"</span>
}
</code></pre>
<p>Lo que nos interesaría es un tipado más específico que tenga en cuenta las claves:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> TFunc = <span class="hljs-function">(<span class="hljs-params">key: <span class="hljs-string">'OPEN'</span> | <span class="hljs-string">'CLOSE'</span></span>) =&gt;</span> <span class="hljs-built_in">string</span>
</code></pre>
<p>La pregunta es: ¿cómo puedo dopar a mi función <code>t</code> para que sepa exactamente qué claves puede recibir?</p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExcDN0d2VnZ2dvbXZpMGFpcXMycGh4Nmthd3pzeGJycm5kYjhsaHl3MCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/dcScHZPUskMOJwyqjj/giphy.gif" alt class="image--center mx-auto" /></p>
<p>Pues <a target="_blank" href="https://github.com/i18next/i18next/pull/1775">desde la v22.0</a> i18next te ofrece una solución. ¿Lo malo? No es automático. Pero no worries, ¡es muy fácil!</p>
<p>Como se explica <a target="_blank" href="https://www.i18next.com/overview/typescript">en la documentación</a>, creamos un archivo de declaración. Se recomienda colocarlo en <code>src/@types/i18next.d.ts</code></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Importamos nuestro JSON para leer las claves</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> jsonKeys <span class="hljs-keyword">from</span> <span class="hljs-string">'../i18n/es.json'</span>

<span class="hljs-comment">// Aumentamos los tipos del módulo i18next</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> 'i18next' {
  <span class="hljs-keyword">interface</span> CustomTypeOptions {
    resources: {
      <span class="hljs-comment">// Añadimos las claves JSON en el namespace por defecto</span>
      translation: <span class="hljs-keyword">typeof</span> jsonKeys
    }
  }
}
</code></pre>
<p>Esto funciona gracias a una característica de TypeScript llamada <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation">aumento de módulos</a>, que permite sobrescribir los tipos de cualquier librería.</p>
<p>La interface <code>CustomTypeOptions</code> la han preparado desde i18next para que podamos meter ahí nuestra estructura JSON, y así i18next la tenga en cuenta.</p>
<h2 id="heading-ya-esta">¡Ya está!</h2>
<p>¿Qué pasa ahora si me equivoco en una clave?</p>
<pre><code class="lang-typescript">&lt;button&gt;{t(<span class="hljs-string">'OPEM'</span>)}&lt;/button&gt;
<span class="hljs-comment">// ❌ Argument of type '["OPEM"]' is not assignable to</span>
<span class="hljs-comment">// parameter of type '[key: "OPEN" | "CLOSE"]'</span>
</code></pre>
<p>✅ Nunca más llegará a producción un <em>typo</em> en una clave de traducción.</p>
<p>✅ Además del <a target="_blank" href="https://code.visualstudio.com/docs/editor/intellisense">IntelliSense</a> mejorado, ya que pulsando <code>ctrl</code> + <code>espacio</code> dentro de <code>t('')</code> recibirás una lista de claves disponibles.</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDl6emwwYnJ2MTlyZnlzc3VlMnpwaDFqOHJ0aXoyczcwcHk2aXUxaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/U4DswrBiaz0p67ZweH/giphy.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[¿Y si paso un state setter como prop?]]></title><description><![CDATA[Trabajando en React es posible que, bajo los entresijos de algún proyecto, te hayas encontrado componentes que reciben alguna prop como esta:
<SomeInputField setValue={setValue} /> // 🤔

Si tienes suficiente experiencia en React puede que evites est...]]></description><link>https://desko.dev/y-si-paso-un-state-setter-como-prop</link><guid isPermaLink="true">https://desko.dev/y-si-paso-un-state-setter-como-prop</guid><category><![CDATA[React]]></category><dc:creator><![CDATA[Ismael Ramon]]></dc:creator><pubDate>Mon, 18 Dec 2023 18:45:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/UYsBCu9RP3Y/upload/3474a2efe38b317549aad4ba923b959b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trabajando en React es posible que, bajo los entresijos de algún proyecto, te hayas encontrado componentes que reciben alguna prop como esta:</p>
<pre><code class="lang-javascript">&lt;SomeInputField setValue={setValue} /&gt; <span class="hljs-comment">// 🤔</span>
</code></pre>
<p>Si tienes suficiente experiencia en React puede que evites esto sin siquiera pensarlo. No obstante, qué bueno es poder razonarlo para ayudar al equipo a estar en la misma página.</p>
<p>Si por el contrario llevas poco tiempo o ya has cogido el vicio, puede que estés pensando que no es tan malo.</p>
<p>Bueno, yo también he estado ahí. <em>Let's talk about this.</em></p>
<h2 id="heading-cual-es-el-problema">Cuál es el problema</h2>
<p>Tal vez sea verdad que hay casos pequeños donde no supone un problema aparente, pero por favor, echa un vistazo a este otro ejemplo:</p>
<pre><code class="lang-javascript">&lt;SomeComponent
  setForm={setForm}
  setCart={setCart}
  setUser={setUser}
  setMeFree={setMeFree}
/&gt;
</code></pre>
<p>Ahora considera estas preguntas básicas:</p>
<ul>
<li><p>¿Cuándo cambia cada estado?</p>
</li>
<li><p>¿Qué o quién los hace cambiar?</p>
</li>
<li><p>¿Cómo cambian exactamente?</p>
</li>
<li><p>¿Dónde está el código que lo hace?</p>
</li>
</ul>
<p>Como puedes observar, el código no nos da información suficiente para responder a ninguna de las preguntas. Básicamente, no sabemos <em>nada</em> de estos estados.</p>
<blockquote>
<p>Pero solo tengo que entrar al código de <code>SomeComponent</code>, ¿no? — Alguien dirá</p>
</blockquote>
<p>Pues vamos adentro:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// En algún lugar de SomeComponent.jsx</span>
&lt;YetAnotherComponent
  setForm={setForm}
  setCart={setCart}
  setUser={setUser}
  setMeFree={setMeFree}
/&gt;
</code></pre>
<p>¡Vaya! Empieza la frustración. Un bucle al más puro estilo <em>Inception</em>, y seguimos sin respuestas.</p>
<p>Es más, los setter pasarán a otros componentes hermanos y seguirán enviándose mediante props a más y más hijos en la jerarquía, multiplicando la cantidad de archivos de código a revisar.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">😵‍💫</div>
<div data-node-type="callout-text">No quieres revisar 15 archivos para entender un estado</div>
</div>

<p>¿Ves cuál es el problema? El estado es un elemento clave de la aplicación ¡y así es difícil saber lo que hace!</p>
<h2 id="heading-como-escapar-de-esto">Cómo escapar de esto</h2>
<p>Volvamos a las preguntas de antes: ¿cuándo cambia el estado? ¿cómo? ¿dónde?... ¿No sería genial poder responderlas sin tener que ir a otros archivos?</p>
<p>Atentos, porque hay una forma y es estándar. Y si existe un estándar es porque presenta ventajas frente a otros métodos.</p>
<p>Seguir estándares suele ahorrar muchos problemas.</p>
<h3 id="heading-fijate-en-el-codigo-nativo">Fíjate en el código nativo</h3>
<p>Definitivamente no hay ninguna prop estándar con nombre de setter.</p>
<blockquote>
<p>Un <code>input</code> no recibe <code>setValue</code></p>
</blockquote>
<p>¿Lo has visto en componentes nativos? ¿kits de UI? ¿librerías populares? Nada.</p>
<p>Lo que sí habrás visto es esto:</p>
<pre><code class="lang-javascript">&lt;input onChange={...} /&gt; <span class="hljs-comment">// 👀</span>
</code></pre>
<p>Así es, un input nativo no ofrece una prop <code>setValue</code>, sino una prop <code>onChange</code>.</p>
<p>Esta prop de evento espera una función, donde típicamente se usa el contenido del argumento <code>event</code> para hacer una actualización de estado:</p>
<pre><code class="lang-javascript">&lt;input onChange={
  <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> setValue(event.target.value)
} /&gt;
</code></pre>
<p>Piensa en la información tan valiosa que tenemos aquí:</p>
<ul>
<li><p>El nombre de la prop ya te da una idea de cuándo sucede</p>
</li>
<li><p>Es fácil imaginar la acción responsable del cambio</p>
</li>
<li><p>Se ve exactamente cómo cambia el estado</p>
</li>
<li><p>Todo el código relevante está a la vista</p>
</li>
</ul>
<p>¿No es genial?</p>
<h3 id="heading-la-clave-division-de-responsabilidad">La clave: división de responsabilidad</h3>
<p>Vamos a señalar el motivo por el que esto funciona tan bien.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">👉</div>
<div data-node-type="callout-text">Hay división de responsabilidad</div>
</div>

<p>Cuando hay división de responsabilidad, cada pieza del sistema tiene un propósito bien definido y delimitado. A menudo se puede entender por sí misma sin acudir al resto.</p>
<p>Esto es importante en esta profesión, porque cuanto menos tiempo tardes en entender, antes podrás empezar a tocar.</p>
<p>Siguiendo con la división de responsabilidad: el <code>input</code> nativo no quiere saber nada de cómo se cambia tu estado, eso es problema de tu componente padre.</p>
<p>El <code>input</code> solo te habla de lo que él controla gracias a la event prop <code>onChange</code>:</p>
<ul>
<li><p>Cuándo ocurre un cambio</p>
</li>
<li><p>Los detalles de ese cambio</p>
</li>
</ul>
<p>Así que, en vez de enviar un <em>state setter</em> como prop, ha llegado el momento de imitar el patrón nativo: las <em>event props</em>.</p>
<h3 id="heading-hazlo-en-tus-componentes">Hazlo en tus componentes</h3>
<p>El estado y sus valores son responsabilidad única del componente que declara el <code>useState</code>. Por ejemplo, si tuvieras un componente que gestiona una lista:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// List.jsx</span>
<span class="hljs-keyword">const</span> [list, setList] = useState([])
</code></pre>
<p>Digamos que vamos a tener un hijo <code>Item</code> con la capacidad de borrarse del listado.</p>
<p>El padre <code>List</code> es responsable de hacer cada <code>setList</code> en su propio código. No delegará esa responsabilidad en <code>Item</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// También en List.jsx</span>
<span class="hljs-keyword">const</span> handleRemoveItem = <span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> setList(list.filter(...))
<span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Item</span> <span class="hljs-attr">onRemove</span>=<span class="hljs-string">{handleRemoveItem}</span> /&gt;</span></span>
</code></pre>
<p><code>Item</code> no debe saber nada sobre la lista completa del padre ni cómo ha de cambiar, pero hay dos cosas que sí son su responsabilidad:</p>
<ul>
<li><p>Cuándo se ha de borrar: porque tendrá un botón para hacerlo</p>
</li>
<li><p>Cuál se ha de borrar: porque sabrá su propio id</p>
</li>
</ul>
<p>Como ya hemos visto, en lugar de recibir <code>setList</code> directamente vamos a exponer una <em>event prop</em> que permita al padre estar al tanto de estas cosas:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Item</span> (<span class="hljs-params">{ onRemove }</span>) </span>{
  <span class="hljs-keyword">const</span> myId = <span class="hljs-number">27</span>
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> onRemove(myId)}&gt;❌<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
}
</code></pre>
<p>Pero <code>Item</code> nunca decidirá qué hacer con un estado que no es suyo, y de hecho desde su código no tendrá acceso explícito a ningún setter ajeno.</p>
<p>Ya está, cada componente tiene su responsabilidad y ninguno invade la del otro. Vamos a mirar el código completo de <code>List</code> una última vez:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ✅</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">List</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [list, setList] = useState([])
  <span class="hljs-keyword">const</span> handleRemoveItem = <span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> setList(list.filter(...))
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Item</span> <span class="hljs-attr">onRemove</span>=<span class="hljs-string">{handleRemoveItem}</span> /&gt;</span></span>
}
</code></pre>
<p>Todas las preguntas sobre el estado tienen respuesta sin salir del archivo:</p>
<ul>
<li><p>¿Cuándo cambia el estado? — Al suceder la acción "borrar"</p>
</li>
<li><p>¿Qué o quién lo hace cambiar? — El usuario que interactúa con <code>Item</code></p>
</li>
<li><p>¿Cómo cambia exactamente? — Con un <code>.filter()</code>, está a la vista</p>
</li>
<li><p>¿Dónde está el código que lo hace? — En el mismo archivo</p>
</li>
</ul>
<p>Pero, solo por saborear una vez más la diferencia:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">List</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [list, setList] = useState([])
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Item</span> <span class="hljs-attr">setList</span>=<span class="hljs-string">{setList}</span> /&gt;</span></span>
}
</code></pre>
<p>En contraste, aquí ya no tendríamos ni idea de cuándo, dónde, cómo o qué se está haciendo con el estado.</p>
<h2 id="heading-conclusion">Conclusión</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">⚡</div>
<div data-node-type="callout-text">Si imitamos el modelo nativo recibimos <em>event props</em>, no <em>state setters</em></div>
</div>

<p>❌ Pasar <em>state setters</em> como props oscurece la lógica de estado fragmentándola en diferentes archivos, lo que resulta en dificultades para entenderla.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [value, setValue] = useState(<span class="hljs-string">''</span>)
&lt;SomeInputField setValue={setValue} /&gt; <span class="hljs-comment">// ???</span>
</code></pre>
<p>✅ El estándar nativo de las <em>event props</em> ayuda a delimitar correctamente la responsabilidad del estado y sus cambios junto al código donde se declara el estado.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [value, setValue] = useState(<span class="hljs-string">''</span>)
&lt;AwesomeInputField onChange={<span class="hljs-function"><span class="hljs-params">next</span> =&gt;</span> setValue(next)} /&gt;
</code></pre>
]]></content:encoded></item></channel></rss>