{"id":10330,"date":"2018-08-17T09:10:14","date_gmt":"2018-08-17T14:10:14","guid":{"rendered":"http:\/\/carterembry.com\/\/?p=10330"},"modified":"2025-08-05T09:05:23","modified_gmt":"2025-08-05T14:05:23","slug":"creating-flexible-and-reusable-react-file-uploaders","status":"publish","type":"post","link":"https:\/\/carterembry.com\/es\/2018\/creating-flexible-and-reusable-react-file-uploaders\/%20","title":{"rendered":"Creaci\u00f3n de cargadores de archivos React flexibles y reutilizables"},"content":{"rendered":"<div id=\"pl-10330\"  class=\"panel-layout\" ><div id=\"pg-10330-0\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-0-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-0-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"0\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Daniel apareci\u00f3 recientemente en el blog Eventbrite Engineering y estamos muy orgullosos del trabajo que hace all\u00ed.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-1\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-1-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-1-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"1\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/Flexible-Reusable-React-File-Uploader.png\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/Flexible-Reusable-React-File-Uploader.png\" width=\"538\" height=\"300\" sizes=\"(max-width: 538px) 100vw, 538px\" title=\"\t\t\t\t\t\t\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-1-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-1-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"2\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>El equipo de Creaci\u00f3n de Eventos de Eventbrite necesitaba un cargador de im\u00e1genes basado en React que proporcionara flexibilidad a la vez que presentara una interfaz de usuario sencilla. Los componentes del cargador de im\u00e1genes deb\u00edan funcionar en diversos escenarios como partes reutilizables que pudieran componerse de forma diferente seg\u00fan las necesidades. Sigue leyendo para ver c\u00f3mo resolvimos este problema.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-2\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-2\" ><div id=\"pgc-10330-2-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-2-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"3\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">\u00bfQu\u00e9 es un cargador de archivos?<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-3\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-3\" ><div id=\"pgc-10330-3-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-3-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"4\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Antes, si quer\u00edas obtener un archivo de tus usuarios web, ten\u00edas que utilizar un\u00a0<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/input\/file\">\"tipo de entrada \"archivo<\/a>. Este planteamiento era limitado en muchos aspectos, sobre todo en que\u00a0<i>es una entrada<\/i>: los datos s\u00f3lo se transmiten cuando env\u00edas el formulario, por lo que los usuarios no tienen la oportunidad de ver los comentarios antes o durante la carga.<\/p>\n<p>Teniendo esto en cuenta, los uploaders de React de los que hablaremos no son entradas de formulario; son \"herramientas de transporte inmediato\". El usuario elige un archivo, el cargador lo transporta a un servidor remoto, y luego recibe una respuesta con alg\u00fan identificador \u00fanico. Ese identificador se asocia inmediatamente a un registro de la base de datos o se introduce en un campo oculto del formulario.<\/p>\n<\/div>\n<\/div><\/div><\/div><div id=\"pgc-10330-3-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-3-1-0\" class=\"so-panel widget widget_sow-video panel-first-child panel-last-child\" data-index=\"5\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-video so-widget-sow-video-default-f379b959fc93-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-video-wrapper\">\n\t\t\t<video\n\t\t\tid=\"sow-player-1\" class=\"sow-video-widget\" preload=\"auto\" style=\"width:100%;height:100%;\" controls>\n\t\t\t\t\t\t\t<source type=\"video\/mp4\" src=\"https:\/\/carterembry.com\/wp-content\/uploads\/2022\/02\/uploaders.mp4\"\/>\n\t\t\t\t\t<\/video>\n\t<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-4\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-4-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-4-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"6\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Esta nueva estrategia proporciona una enorme flexibilidad respecto a los procesos de carga tradicionales. Por ejemplo, desacoplar el transporte de archivos del env\u00edo de formularios nos permite\u00a0<strong>subir directamente al almacenamiento de terceros (como Amazon S3) sin enviar los archivos a trav\u00e9s de nuestros servidores<\/strong>.<\/p>\n<p>La contrapartida de esta flexibilidad es la complejidad; los cargadores de archivos de arrastrar y soltar son bestias complejas. Tambi\u00e9n necesit\u00e1bamos que nuestro cargador React fuera sencillo y f\u00e1cil de usar. Encontrar un camino que proporcionara tanto flexibilidad como facilidad de uso no fue tarea f\u00e1cil.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-5\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-5-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-5-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"7\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Identificar responsabilidades<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Establecer las responsabilidades de un uploader parece f\u00e1cil... se sube, \u00bfno? Pues claro, pero hay muchas otras cosas implicadas para que eso ocurra:<\/p>\n<ul>\n<li>Debe tener una zona de ca\u00edda que cambie en funci\u00f3n de la interacci\u00f3n del usuario. Si el usuario arrastra un archivo sobre ella, debe indicar este cambio de estado.<\/li>\n<li>\u00bfQu\u00e9 pasa si nuestros usuarios no pueden arrastrar archivos? Puede que tengan problemas de accesibilidad o que intenten subir archivos desde su tel\u00e9fono. En cualquier caso, nuestro cargador debe mostrar un selector de archivos cuando el usuario haga clic o toque.<\/li>\n<li>Debe validar el archivo elegido para asegurarse de que es de un tipo y tama\u00f1o aceptables.<\/li>\n<li>Una vez elegido un archivo, deber\u00eda mostrar una vista previa del mismo mientras se carga.<\/li>\n<li>Debe dar una respuesta significativa al usuario mientras se est\u00e1 cargando, como una barra de progreso o un gr\u00e1fico de carga que comunique que algo est\u00e1 ocurriendo.<\/li>\n<li>Adem\u00e1s, \u00bfqu\u00e9 pasa si falla? Debe mostrar un error significativo al usuario para que sepa que debe intentarlo de nuevo (o abandonar).<\/li>\n<li>Ah, y realmente tiene que subir el archivo.<\/li>\n<\/ul>\n<p>Estas responsabilidades son s\u00f3lo una peque\u00f1a lista, pero ya te haces una idea, pueden complicarse muy r\u00e1pidamente. Adem\u00e1s, al cargar\u00a0<i>im\u00e1genes<\/i>\u00a0es nuestro caso de uso principal, podr\u00eda haber diversas necesidades de carga de archivos. Si te has tomado la molestia de averiguar el comportamiento de arrastrar \/ soltar \/ resaltar \/ validar \/ transportar \/ \u00e9xito \/ fracaso, \u00bfpor qu\u00e9 escribirlo todo de nuevo cuando de repente necesitas subir CSV para ese \u00fanico informe?<\/p>\n<p>Entonces, \u00bfc\u00f3mo podemos estructurar nuestro cargador de im\u00e1genes React para obtener la m\u00e1xima flexibilidad y reutilizaci\u00f3n?<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-6\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-6\" ><div id=\"pgc-10330-6-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-6-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"8\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Separaci\u00f3n de preocupaciones<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-7\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-7\" ><div id=\"pgc-10330-7-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-7-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"9\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__02.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__02.jpeg\" width=\"1152\" height=\"414\" sizes=\"(max-width: 1152px) 100vw, 1152px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-7-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-7-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"10\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>En el siguiente diagrama puedes ver una visi\u00f3n general de nuestro enfoque previsto. No te preocupes si te parece complicado: a continuaci\u00f3n profundizaremos en cada uno de estos componentes para ver los detalles sobre su finalidad y funci\u00f3n.<\/p>\n<p>Nuestra biblioteca de componentes basada en React no deber\u00eda tener que saber c\u00f3mo funcionan nuestras API. Mantener esta l\u00f3gica separada tiene la ventaja a\u00f1adida de la reutilizaci\u00f3n; los distintos productos no est\u00e1n encerrados en una \u00fanica API, ni siquiera en un \u00fanico estilo de API. En su lugar, pueden reutilizar tanto o tan poco como necesiten.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-8\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-8-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-8-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"11\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Incluso dentro de\u00a0<a href=\"https:\/\/medium.com\/@dan_abramov\/smart-and-dumb-components-7ca2f9a7c7d0\">componentes de presentaci\u00f3n<\/a>, existe la oportunidad de separar la funci\u00f3n de la presentaci\u00f3n. As\u00ed que tomamos nuestra lista de responsabilidades y creamos una pila de componentes, desde los m\u00e1s generales en la parte inferior hasta los m\u00e1s espec\u00edficos en la superior.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-9\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-9\" ><div id=\"pgc-10330-9-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-9-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"12\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Componentes fundamentales<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<h4>UploaderDropzone<\/h4>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-10\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-10\" ><div id=\"pgc-10330-10-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-10-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"13\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__03.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__03.jpeg\" width=\"1152\" height=\"413\" sizes=\"(max-width: 1152px) 100vw, 1152px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-10-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-10-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"14\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Este componente es el coraz\u00f3n de la interfaz de usuario del cargador, donde comienza la acci\u00f3n. Se encarga de los eventos de arrastrar y soltar, as\u00ed como de hacer clic para navegar. No tiene estado, s\u00f3lo sabe c\u00f3mo normalizar y reaccionar (\u00bfves lo que he hecho?) a determinadas acciones del usuario. Acepta retrollamadas como props para poder informar a su implementador de cu\u00e1ndo ocurren las cosas.<\/p>\n<p>Escucha si se arrastran archivos sobre \u00e9l, y luego invoca una llamada de retorno. Del mismo modo, cuando se elige un archivo, ya sea mediante arrastrar\/soltar o haciendo clic para navegar, invoca otra llamada de retorno con el objeto archivo JS.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-11\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-11-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-11-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"15\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Tiene una de esas entradas de tipo \"archivo\" que he mencionado antes oculta en su interior para los usuarios que no pueden (o prefieren no) arrastrar archivos. Esta funcionalidad es importante, y al abstraerla aqu\u00ed, los componentes que utilicen el dropzone no tienen que pensar en c\u00f3mo se eligi\u00f3 el archivo.<\/p>\n<p>A continuaci\u00f3n se muestra un ejemplo de UploaderDropzone utilizando React:<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-12\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-12\" ><div id=\"pgc-10330-12-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-12-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child\" data-index=\"16\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n\n\u00a1Arrastra un archivo aqu\u00ed!\n\n<\/pre>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-12-0-1\" class=\"so-panel widget widget_sow-editor panel-last-child\" data-index=\"17\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>UploaderDropzone tiene muy poca opini\u00f3n sobre su aspecto, por lo que su estilo es m\u00ednimo. Por ejemplo,\u00a0<a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=656164\">algunos navegadores tratan los eventos de arrastre de forma diferente<\/a>\u00a0cuando se producen en descendientes profundos del nodo objetivo. Para solucionar este problema, la zona de soltar utiliza un \u00fanico div transparente para cubrir a todos sus descendientes. Esto proporciona la experiencia necesaria a los usuarios que arrastran\/sueltan, pero tambi\u00e9n\u00a0<strong>mantiene la accesibilidad para lectores de pantalla y otras tecnolog\u00edas de asistencia.<\/strong><\/p>\n<\/div>\n<\/div><\/div><\/div><div id=\"pgc-10330-12-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-12-1-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"18\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__04.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__04.jpeg\" width=\"725\" height=\"458\" sizes=\"(max-width: 725px) 100vw, 725px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-13\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-13\" ><div id=\"pgc-10330-13-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-13-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"19\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__05.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__05.jpeg\" width=\"761\" height=\"471\" sizes=\"(max-width: 761px) 100vw, 761px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-13-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-13-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"20\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<h4>UploaderLayoutManager<\/h4>\n<p>El componente UploaderLayoutManager gestiona la mayor\u00eda de las transiciones de estado y sabe qu\u00e9 dise\u00f1o debe mostrarse en cada paso del proceso, al tiempo que acepta otros componentes React como props para cada paso.<\/p>\n<p><strong>Esto permite a los ejecutores pensar en cada paso como una idea visual separada, sin preocuparse de c\u00f3mo y cu\u00e1ndo se produce cada transici\u00f3n<\/strong>. Los partidarios de este componente React s\u00f3lo tienen que pensar en qu\u00e9 dise\u00f1o debe ser visible en un momento dado en funci\u00f3n del estado, no en c\u00f3mo se rellenan los archivos o c\u00f3mo debe ser el dise\u00f1o.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-14\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-14-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-14-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child\" data-index=\"21\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Aqu\u00ed tienes una lista de pasos que puedes proporcionar al LayoutManager como accesorios:<\/p>\n<ul>\n<li>Pasos gestionados por LayoutManager:\n<ul>\n<li>Despoblado - un dropzone vac\u00edo con una llamada a la acci\u00f3n (\"\u00a1Sube una gran imagen!\")<\/li>\n<li>Archivo arrastrado sobre la ventana pero no sobre la zona de ca\u00edda (\"\u00a1Suelta ese archivo aqu\u00ed!\")<\/li>\n<li>Archivo arrastrado sobre dropzone (\"\u00a1Su\u00e9ltalo ahora!\")<\/li>\n<li>Carga de archivo en curso (\"Espera, lo estoy enviando...\")<\/li>\n<\/ul>\n<\/li>\n<li>Paso gestionado por componente que implementa LayoutManager:\n<ul>\n<li>El archivo se ha cargado y se ha rellenado. Para nuestro cargador de im\u00e1genes, esto es una vista previa de la imagen con un bot\u00f3n \"Eliminar\".<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-14-0-1\" class=\"so-panel widget widget_sow-editor\" data-index=\"22\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>El propio LayoutManager tiene poco o ning\u00fan estilo, y s\u00f3lo muestra los elementos visuales que se le han pasado como props. Es responsable de mantener qu\u00e9 paso del proceso ha alcanzado el usuario y de mostrar alg\u00fan contenido para ese paso.<\/p>\n<p>El \u00fanico paso del dise\u00f1o que se gestiona externamente es \"Vista previa\" (si el cargador tiene una imagen rellenada). Esto se debe a que el componente de implementaci\u00f3n necesita definir el estado en el que se inicia el cargador. Por ejemplo, si el usuario ha subido previamente una imagen, queremos mostrar esa imagen cuando vuelva a la p\u00e1gina.<\/p>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-14-0-2\" class=\"so-panel widget widget_sow-editor\" data-index=\"23\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Ejemplo de uso de LayoutManager:<\/p>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-14-0-3\" class=\"so-panel widget widget_sow-editor panel-last-child\" data-index=\"24\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\ndropzoneElement={}\n    windowDragDropzoneElement={}\n    dragDropzoneElement={}\n    loadingElement={}\n    vista previaElement={}\n \n    showPreview={!archivo}\n \n    onRecibirArchivo={manejarRecibirArchivo}\n\/&gt;\n<\/pre>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-15\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-15\" ><div id=\"pgc-10330-15-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-15-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"25\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Componentes espec\u00edficos de los recursos<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<h4>Cargador de im\u00e1genes<\/h4>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-16\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-16\" ><div id=\"pgc-10330-16-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-16-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"26\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__06.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__06.jpeg\" width=\"1152\" height=\"414\" sizes=\"(max-width: 1152px) 100vw, 1152px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-16-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-16-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"27\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>El componente ImageUploader est\u00e1 orientado casi por completo a la presentaci\u00f3n: definir el aspecto de cada paso y pasarlos como props a un UploadLayoutManager. Tambi\u00e9n es un buen lugar para hacer validaciones (tipo de archivo, tama\u00f1o del archivo, etc.).<\/p>\n<p>Los usuarios de esta herramienta pueden centrarse casi por completo en el aspecto visual del cargador. Este componente mantiene muy poca l\u00f3gica, ya que las transiciones de estado las gestiona el UploaderLayoutManager.\u00a0<strong>Podemos cambiar los elementos visuales de forma fluida sin preocuparnos apenas de da\u00f1ar la funci\u00f3n del cargador.<\/strong><\/p>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-17\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-17-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-17-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child\" data-index=\"28\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Ejemplo ImageUploader:<\/p>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-17-0-1\" class=\"so-panel widget widget_sow-editor panel-last-child\" data-index=\"29\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst DropzoneLayout = () =&amp;gt; (\n    &lt;p&gt;Arrastra un archivo aqu&iacute; o haz clic para examinar&lt;\/p&gt;\n);\nconst ArrastrarDropzoneLayout = () =&amp;gt; (\n    &lt;p&gt;&iexcl;Suelta el archivo ahora!&lt;\/p&gt;\n);\nconst LoadingLayout = () =&amp;gt; (\n    &lt;p&gt;Espera, cargando...&lt;\/p&gt;\n);\nconst PreviewLayout = ({archivo, onRemove}) =&amp;gt; (\n    &lt;div&gt;\n        &lt;p&gt;Nombre: {nombre.archivo}&lt;\/p&gt;\n        &lt;button onclick=&quot;{onRemove}&quot;&gt;Eliminar archivo&lt;\/button&gt;\n    &lt;\/div&gt;\n);\nclase ImageUploader extends React.Component {\n    state = {archivo: indefinido};\n \n    _handleRemove = () =&amp;gt; this.setState({archivo: undefined});\n \n    _handleRecibirArchivo = (archivo) =&amp;gt; {\n        this.setState({archivo});\n \n        return new Promise((resolver, rechazar) =&amp;gt; {\n            \/\/ &iexcl;sube el archivo!\n        })\n        .catch(() =&amp;gt; this.setState({archivo: indefinido}))\n    }\n \n    render() {\n        let {archivo} = this.estado\n        let vista previa;\n \n        si (archivo) {\n            vista previa = (\n                &lt;previewlayout file=&quot;{file}&quot; onremove=&quot;{this._handleRemove}&quot;&gt;&lt;\/previewlayout&gt;\n            );\n        }\n \n        devolver (\n            &lt;uploaderlayoutmanager\n                dropzoneelement=&quot;{&lt;DropzoneLayout&quot;&gt;&lt;\/uploaderlayoutmanager&gt;}\n                dragDropzoneElement={&lt;dragdropzonelayout&gt;&lt;\/dragdropzonelayout&gt;}\n                loadingElement={&lt;loadinglayout&gt;&lt;\/loadinglayout&gt;}\n                previewElement={prevista}\n                showPreview={!archivo}\n                onRecibirArchivo={this._handleRecibirArchivo}\n            \/&amp;gt;\n        );\n    }\n};\n<\/pre>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-18\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-18\" ><div id=\"pgc-10330-18-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-18-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"30\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Capa espec\u00edfica de la aplicaci\u00f3n<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-19\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-19\" ><div id=\"pgc-10330-19-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-19-0-0\" class=\"so-panel widget widget_sow-image panel-first-child panel-last-child\" data-index=\"31\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-image so-widget-sow-image-default-dbf295114b96-10330\"\n\t\t\t\n\t\t>\n<div class=\"sow-image-container\">\n\t\t\t<a href=\"http:\/\/carterembry.com\/\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__07.jpeg\"\n\t\t\t\t\t>\n\t\t\t<img \n\tsrc=\"https:\/\/carterembry.com\/wp-content\/uploads\/2018\/08\/flexible_resuable_react_uploader__07.jpeg\" width=\"1152\" height=\"415\" sizes=\"(max-width: 1152px) 100vw, 1152px\" alt=\"\" \t\tclass=\"so-widget-image\" loading=\"lazy\" \/>\n\t\t\t<\/a><\/div>\n\n<\/div><\/div><\/div><div id=\"pgc-10330-19-1\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-19-1-0\" class=\"so-panel widget widget_sow-editor panel-first-child panel-last-child\" data-index=\"32\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>El ejemplo anterior tiene un aspecto destacado que no tiene que ver con la presentaci\u00f3n: el transporte de archivos que ocurre en _handleReceiveFile. Queremos que este componente ImageUploader viva en nuestra biblioteca de componentes y est\u00e9 desacoplado del comportamiento espec\u00edfico de la API, as\u00ed que tenemos que eliminar eso. Afortunadamente, es tan sencillo como aceptar una funci\u00f3n mediante props que devuelva una promesa que se resuelva cuando se complete la carga.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><\/div><div id=\"pg-10330-20\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-10330-20-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-20-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child\" data-index=\"33\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n_handleRecibirArchivo(archivo) {\n    \/\/ podr\u00eda hacer aqu\u00ed la validaci\u00f3n del archivo antes del transporte. Si el archivo no pasa la validaci\u00f3n, devuelve una promesa rechazada.\n    let {uploadImage} = this.props;\n \n    this.setState({archivo});\n \n    return subirImagen(archivo)\n        .catch(() =&gt; this.setState({archivo: indefinido}))\n}\n<\/pre>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-20-0-1\" class=\"so-panel widget widget_sow-editor\" data-index=\"34\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Con este peque\u00f1o cambio, este mismo cargador de im\u00e1genes puede utilizarse para diversas aplicaciones.\u00a0<strong>Una parte de tu aplicaci\u00f3n puede subir im\u00e1genes directamente a un tercero (como Amazon S3), mientras que otra puede subirlas a un servidor local con una finalidad y un manejo totalmente distintos, pero utilizando la misma presentaci\u00f3n visual.<\/strong><\/p>\n<p>Y ahora, como toda esa complejidad est\u00e1 compartimentada en cada componente, el ImageUploader tiene una implementaci\u00f3n muy limpia:<\/p>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-20-0-2\" class=\"so-panel widget widget_sow-editor\" data-index=\"35\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n&lt;imageuploader uploadImage={S3ImageUploadApi}&gt;&lt;\/imageuploader&gt;\n<\/pre>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-20-0-3\" class=\"so-panel widget widget_sow-editor panel-last-child\" data-index=\"36\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Con esta base, las aplicaciones pueden utilizar este mismo ImageUploader de diversas formas. Hemos proporcionado la flexibilidad deseada manteniendo la API limpia y sencilla. Se pueden construir nuevas envolturas sobre UploadLayoutManager para manejar otros tipos de archivo o nuevos dise\u00f1os.<\/p>\n<\/div>\n<\/div><\/div><\/div><\/div><div id=\"pg-10330-21\"  class=\"panel-grid panel-has-style\" ><div class=\"panel-row-style panel-row-style-for-10330-21\" ><div id=\"pgc-10330-21-0\"  class=\"panel-grid-cell\" ><div id=\"panel-10330-21-0-0\" class=\"so-panel widget widget_sow-editor panel-first-child\" data-index=\"37\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-editor so-widget-sow-editor-base\"\n\t\t\t\n\t\t><h3 class=\"widget-title\">Para terminar<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Imagina cargadores de im\u00e1genes creados espec\u00edficamente para cada escenario, pero que s\u00f3lo contienen unos pocos componentes sencillos hechos de marcas de presentaci\u00f3n.  Cada uno de ellos puede utilizar la misma funcionalidad de carga, si tiene sentido, pero con una presentaci\u00f3n totalmente diferente. O dale la vuelta a la idea, utilizando los mismos elementos visuales de carga pero con interfaces API totalmente diferentes.<\/p>\n<p>\u00bfDe qu\u00e9 otras formas utilizar\u00edas estos componentes b\u00e1sicos? \u00bfQu\u00e9 otros cargadores construir\u00edas? El cielo es el l\u00edmite si te tomas el tiempo de construir componentes reutilizables.<\/p>\n<\/div>\n<\/div><\/div><div id=\"panel-10330-21-0-1\" class=\"so-panel widget widget_sow-button-grid panel-last-child\" data-index=\"38\" ><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-button-grid so-widget-sow-button-grid-default-129192d28302-10330\"\n\t\t\t\n\t\t>\t<div class=\"sow-buttons-grid\">\n\t\t<div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-button so-widget-sow-button-flat-66466acb9e12\"\n\t\t\t\n\t\t><div class=\"ow-button-base ow-button-align-center\"\n>\n\t\t\t<a\n\t\t\t\t\thref=\"https:\/\/www.eventbrite.com\/engineering\/create-reusable-react-file-uploaders\/\"\n\t\t\t\t\tclass=\"sowb-button ow-icon-placement-left ow-button-hover\" target=\"_blank\" rel=\"noopener noreferrer\" \t>\n\t\t<span>\n\t\t\t\n\t\t\tEncuentra el post original en Eventbrite Engineering\t\t<\/span>\n\t\t\t<\/a>\n\t<\/div>\n<\/div><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-button so-widget-sow-button-flat-66466acb9e12\"\n\t\t\t\n\t\t><div class=\"ow-button-base ow-button-align-center\"\n>\n\t\t\t<a\n\t\t\t\t\thref=\"https:\/\/reactnewsletter.com\/issues\/130\"\n\t\t\t\t\tclass=\"sowb-button ow-icon-placement-left ow-button-hover\" target=\"_blank\" rel=\"noopener noreferrer\" \t>\n\t\t<span>\n\t\t\t\n\t\t\tDestacado en el n\u00famero #130 del Bolet\u00edn React\t\t<\/span>\n\t\t\t<\/a>\n\t<\/div>\n<\/div><div\n\t\t\t\n\t\t\tclass=\"so-widget-sow-button so-widget-sow-button-flat-66466acb9e12\"\n\t\t\t\n\t\t><div class=\"ow-button-base ow-button-align-center\"\n>\n\t\t\t<a\n\t\t\t\t\thref=\"https:\/\/react.statuscode.com\/issues\/102\"\n\t\t\t\t\tclass=\"sowb-button ow-icon-placement-left ow-button-hover\" target=\"_blank\" rel=\"noopener noreferrer\" \t>\n\t\t<span>\n\t\t\t\n\t\t\tDestacado en la edici\u00f3n #102 de React Status\t\t<\/span>\n\t\t\t<\/a>\n\t<\/div>\n<\/div>\t<\/div>\n\t<\/div><\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>Daniel apareci\u00f3 recientemente en el blog de ingenier\u00eda de Eventbrite y estamos muy orgullosos del trabajo que hace all\u00ed. El equipo de Creaci\u00f3n de Eventos de Eventbrite necesitaba un cargador de im\u00e1genes basado en React que proporcionara flexibilidad y, al mismo tiempo, presentara una interfaz de usuario sencilla. Los componentes del cargador de im\u00e1genes deb\u00edan funcionar en diversos escenarios como [...]<\/p>","protected":false},"author":3,"featured_media":13450,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wprm-recipe-roundup-name":"","wprm-recipe-roundup-description":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"default","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[10,5,7],"tags":[85,106,112,168,175,199,238,292],"class_list":["post-10330","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-daniel","category-professional-opinions","category-technical-jargon","tag-component","tag-engineering","tag-eventbrite","tag-image-uploader","tag-javascript","tag-nashville","tag-react","tag-uploader"],"_links":{"self":[{"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/posts\/10330","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/comments?post=10330"}],"version-history":[{"count":12,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/posts\/10330\/revisions"}],"predecessor-version":[{"id":21449,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/posts\/10330\/revisions\/21449"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/media\/13450"}],"wp:attachment":[{"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/media?parent=10330"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/categories?post=10330"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/carterembry.com\/es\/wp-json\/wp\/v2\/tags?post=10330"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}