{"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\/it\/2018\/creating-flexible-and-reusable-react-file-uploaders\/%20","title":{"rendered":"Creare caricatori di file React flessibili e riutilizzabili"},"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 \u00e8 stato recentemente presentato nel blog di Eventbrite Engineering e siamo molto orgogliosi del lavoro che svolge.<\/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>Il team di creazione di eventi di Eventbrite aveva bisogno di un caricatore di immagini basato su React che offrisse flessibilit\u00e0 pur presentando un'interfaccia utente semplice. I componenti del caricatore di immagini dovevano funzionare in una variet\u00e0 di scenari come parti riutilizzabili che potevano essere composte in modo diverso a seconda delle esigenze. Continua a leggere per scoprire come abbiamo risolto questo 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\">Cos'\u00e8 un caricatore di file?<\/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>In passato, se volevi ottenere un file dai tuoi utenti web, dovevi usare un file\u00a0<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/input\/file\">\"Tipo di input \"file<\/a>. Questo approccio era limitato in molti modi, soprattutto per il fatto che\u00a0<i>\u00e8 un input<\/i>I dati vengono trasmessi solo al momento dell'invio del modulo, quindi gli utenti non hanno la possibilit\u00e0 di vedere il feedback prima o durante il caricamento.<\/p>\n<p>Per questo motivo, gli uploader di React di cui parleremo non sono dei form input, ma degli \"strumenti di trasporto immediato\". L'utente sceglie un file, l'uploader lo trasporta su un server remoto e riceve una risposta con un identificatore unico. Questo identificativo viene immediatamente associato a un record del database o inserito in un campo nascosto del modulo.<\/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>Questa nuova strategia offre un'enorme flessibilit\u00e0 rispetto ai processi di caricamento tradizionali. Ad esempio, il disaccoppiamento del trasporto dei file dall'invio dei moduli ci permette di\u00a0<strong>caricare direttamente su archivi di terze parti (come Amazon S3) senza inviare i file attraverso i nostri server<\/strong>.<\/p>\n<p>Il compromesso per questa flessibilit\u00e0 \u00e8 la complessit\u00e0: i caricatori di file con il drag-and-drop sono bestie complesse. Avevamo anche bisogno che il nostro uploader React fosse semplice e usabile. Trovare un percorso che garantisse sia la flessibilit\u00e0 che la facilit\u00e0 d'uso non \u00e8 stato facile.<\/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\">Identificazione delle responsabilit\u00e0<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Stabilire le responsabilit\u00e0 di un uploader sembra facile... si carica, giusto? Certo, ma ci sono molte altre cose da fare perch\u00e9 ci\u00f2 avvenga:<\/p>\n<ul>\n<li>Deve avere una zona di caduta che cambia in base all'interazione dell'utente. Se l'utente trascina un file su di essa, deve indicare questo cambiamento di stato.<\/li>\n<li>E se i nostri utenti non possono trascinare i file? Forse hanno problemi di accessibilit\u00e0 o forse stanno cercando di caricare dal loro telefono. In ogni caso, il nostro uploader deve mostrare un selezionatore di file quando l'utente fa clic o tocca.<\/li>\n<li>Deve convalidare il file scelto per assicurarsi che sia di tipo e dimensioni accettabili.<\/li>\n<li>Una volta scelto un file, dovrebbe mostrarne un'anteprima durante il caricamento.<\/li>\n<li>Dovrebbe fornire all'utente un feedback significativo durante il caricamento, come una barra di avanzamento o un grafico di caricamento che comunichi che sta accadendo qualcosa.<\/li>\n<li>Inoltre, cosa succede se fallisce? Deve mostrare all'utente un errore significativo, in modo che sappia che deve riprovare (o rinunciare).<\/li>\n<li>Inoltre, deve caricare il file.<\/li>\n<\/ul>\n<p>Queste responsabilit\u00e0 sono solo un breve elenco, ma ti rendi conto che possono diventare complicate molto rapidamente. Inoltre, durante il caricamento\u00a0<i>immagini<\/i>\u00a0\u00e8 il nostro caso d'uso principale, ma le esigenze di caricamento dei file potrebbero essere molteplici. Se ti sei preso la briga di capire il comportamento di trascinamento \/ rilascio \/ evidenziazione \/ convalida \/ trasporto \/ successo \/ fallimento, perch\u00e9 scrivere tutto di nuovo quando improvvisamente hai bisogno di caricare CSV per quel report?<\/p>\n<p>Quindi, come possiamo strutturare il nostro React Image Uploader per ottenere la massima flessibilit\u00e0 e riusabilit\u00e0?<\/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\">Separazione delle preoccupazioni<\/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>Nel diagramma seguente puoi vedere una panoramica del nostro approccio. Non preoccuparti se ti sembra complicato: di seguito analizzeremo ciascuno di questi componenti per conoscere nel dettaglio il loro scopo e il loro ruolo.<\/p>\n<p>La nostra libreria di componenti basata su React non dovrebbe conoscere il funzionamento delle nostre API. Mantenere questa logica separata ha l'ulteriore vantaggio della riusabilit\u00e0: i diversi prodotti non sono vincolati a un'unica API o addirittura a un unico stile di API. Al contrario, possono riutilizzare tutto o parte del materiale di cui hanno bisogno.<\/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>Anche all'interno di\u00a0<a href=\"https:\/\/medium.com\/@dan_abramov\/smart-and-dumb-components-7ca2f9a7c7d0\">componenti di presentazione<\/a>\u00c8 possibile separare la funzione dalla presentazione. Quindi abbiamo preso il nostro elenco di responsabilit\u00e0 e abbiamo creato una pila di componenti, dal pi\u00f9 generale in basso al pi\u00f9 specifico in alto.<\/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\">Componenti fondamentali<\/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>Questo componente \u00e8 il cuore dell'interfaccia utente dell'uploader, dove inizia l'azione. Gestisce gli eventi di trascinamento e di click-to-browse. Non ha uno stato, ma sa solo come normalizzare e reagire (vedi cosa ho fatto?) a determinate azioni dell'utente. Accetta callback come props in modo da poter dire a chi lo implementa quando accadono delle cose.<\/p>\n<p>Ascolta i file che vengono trascinati su di esso e invoca una callback. Allo stesso modo, quando viene scelto un file, attraverso il trascinamento o il click-to-browse, invoca un'altra callback con l'oggetto file 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>Ha uno di quegli input di tipo \"file\" che ho menzionato prima, nascosto all'interno per gli utenti che non possono (o preferiscono) trascinare i file. Questa funzionalit\u00e0 \u00e8 importante e, astraendola, i componenti che utilizzano la dropzone non devono pensare a come \u00e8 stato scelto il file.<\/p>\n<p>Quello che segue \u00e8 un esempio di UploaderDropzone che utilizza 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\nTrascina un file qui!\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 ha pochissime opinioni sul suo aspetto e quindi ha uno stile minimo. Ad esempio,\u00a0<a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=656164\">Alcuni browser trattano gli eventi di trascinamento in modo diverso<\/a>\u00a0quando si verificano su discendenti profondi del nodo di destinazione. Per risolvere questo problema, la dropzone utilizza un unico div trasparente per coprire tutti i suoi discendenti. In questo modo si ottiene l'esperienza necessaria per gli utenti che trascinano e rilasciano, ma anche\u00a0<strong>mantiene l'accessibilit\u00e0 per gli screen reader e altre tecnologie assistive.<\/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>Il componente UploaderLayoutManager gestisce la maggior parte delle transizioni di stato e sa quale layout deve essere visualizzato per ogni fase del processo, accettando altri componenti React come oggetti di scena per ogni fase.<\/p>\n<p><strong>Questo permette agli implementatori di pensare a ogni fase come a un'idea visiva separata, senza preoccuparsi di come e quando avviene ogni transizione.<\/strong>. I sostenitori di questo componente React devono solo pensare a quale layout deve essere visibile in un determinato momento in base allo stato, non a come vengono popolati i file o a come deve apparire il layout.<\/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>Ecco un elenco di passi che possono essere forniti al LayoutManager come oggetti di scena:<\/p>\n<ul>\n<li>Passi gestiti da LayoutManager:\n<ul>\n<li>Non popolata: una dropzone vuota con una call-to-action (\"Carica un'immagine fantastica!\")<\/li>\n<li>File trascinato sulla finestra ma non sulla dropzone (\"Lascia qui il file!\")<\/li>\n<li>File trascinato sulla dropzone (\"Lascialo subito!\")<\/li>\n<li>Caricamento di file in corso (\"Aspetta, lo sto inviando...\")<\/li>\n<\/ul>\n<\/li>\n<li>Passo gestito da un componente che implementa LayoutManager:\n<ul>\n<li>Il file \u00e8 stato caricato ed \u00e8 popolato. Per il nostro caricatore di immagini, questa \u00e8 un'anteprima dell'immagine con un pulsante \"Rimuovi\".<\/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>Il LayoutManager stesso ha pochi o nessun stile e visualizza solo le immagini che gli sono state passate come oggetti di scena. \u00c8 responsabile di sapere quale fase del processo \u00e8 stata raggiunta dall'utente e di visualizzare i contenuti relativi a quella fase.<\/p>\n<p>L'unica fase del layout gestita esternamente \u00e8 \"Anteprima\" (se l'Uploader ha un'immagine popolata). Questo perch\u00e9 il componente di implementazione deve definire lo stato in cui inizia l'uploader. Ad esempio, se l'utente ha caricato un'immagine in precedenza, vogliamo mostrarla quando torna sulla pagina.<\/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>Esempio di utilizzo di 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=\"\">\n&lt;uploaderlayoutmanager\n    dropzoneElement={}\n    windowDragDropzoneElement={}\n    dragDropzoneElement={}\n    loadingElement={}\n    previewElement={}\n \n    showPreview={!!file}\n \n    onReceiveFile={handleReceiveFile}\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\">Componenti specifici per le risorse<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<h4>Caricatore di immagini<\/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>Il componente ImageUploader \u00e8 quasi interamente orientato alla presentazione: definisce l'aspetto e l'atmosfera di ogni fase e li passa come oggetti di scena a un UploadLayoutManager. \u00c8 anche un ottimo punto di riferimento per la convalida (tipo di file, dimensione del file, ecc.).<\/p>\n<p>I sostenitori di questo strumento possono concentrarsi quasi esclusivamente sull'aspetto visivo dell'uploader. Questo componente mantiene pochissima logica poich\u00e9 le transizioni di stato sono gestite dall'UploaderLayoutManager.\u00a0<strong>Possiamo cambiare le immagini in modo fluido e senza preoccuparci di danneggiare il funzionamento dell'uploader.<\/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>Esempio di 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;Trascina un file qui o clicca per sfogliarlo&lt;\/p&gt;\n);\nconst DragDropzoneLayout = () =&amp;gt; (\n    &lt;p&gt;Lascia il file ora!&lt;\/p&gt;\n);\nconst LoadingLayout = () =&amp;gt; (\n    &lt;p&gt;Attendere, caricamento...&lt;\/p&gt;\n);\nconst PreviewLayout = ({file, onRemove}) =&amp;gt; (\n    &lt;div&gt;\n        &lt;p&gt;Nome: {file.name}&lt;\/p&gt;\n        &lt;button onclick=&quot;{onRemove}&quot;&gt;Rimuovi il file&lt;\/button&gt;\n    &lt;\/div&gt;\n);\nclass ImageUploader extends React.Component {\n    state = {file: undefined};\n \n    _handleRemove = () =&amp;gt; this.setState({file: undefined});\n \n    _handleReceiveFile = (file) =&amp;gt; {\n        this.setState({file});\n \n        return new Promise((resolve, reject) =&amp;gt; {\n            \/\/ carica il file!\n        })\n        .catch(() =&amp;gt; this.setState({file: undefined}))\n    }\n \n    render() {\n        let {file} = this.state;\n        lascia che l&#039;anteprima;\n \n        if (file) {\n            anteprima = (\n                &lt;previewlayout file=&quot;{file}&quot; onremove=&quot;{this._handleRemove}&quot;&gt;&lt;\/previewlayout&gt;\n            );\n        }\n \n        return (\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={preview}\n                showPreview={!!file}\n                onReceiveFile={this._handleReceiveFile}\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\">Livello specifico dell'applicazione<\/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>L'esempio precedente ha un aspetto importante che non riguarda la presentazione: il trasporto dei file che avviene in _handleReceiveFile. Vogliamo che questo componente ImageUploader viva nella nostra libreria di componenti e sia disaccoppiato dal comportamento specifico dell'API, quindi dobbiamo rimuoverlo. Fortunatamente, \u00e8 semplice accettare una funzione tramite i props che restituisce una promessa che si risolve al termine del caricamento.<\/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_handleReceiveFile(file) {\n    \/\/ potrebbe eseguire la convalida del file prima del trasporto. Se il file non viene convalidato, restituisce una promessa rifiutata.\n    let {uploadImage} = this.props;\n \n    this.setState({file});\n \n    return uploadImage(file)\n        .catch(() =&gt; this.setState({file: undefined}))\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 questa piccola modifica, lo stesso caricatore di immagini pu\u00f2 essere utilizzato per diverse applicazioni.\u00a0<strong>Una parte della tua applicazione pu\u00f2 caricare le immagini direttamente su una terza parte (come Amazon S3), mentre un'altra pu\u00f2 caricarle su un server locale per uno scopo e una gestione totalmente diversi, ma utilizzando la stessa presentazione visiva.<\/strong><\/p>\n<p>Ora, poich\u00e9 tutta questa complessit\u00e0 \u00e8 compartimentata in ogni componente, ImageUploader ha un'implementazione molto pulita:<\/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 questa base le applicazioni possono utilizzare lo stesso ImageUploader in diversi modi. Abbiamo fornito la flessibilit\u00e0 desiderata mantenendo l'API pulita e semplice. \u00c8 possibile creare nuovi wrapper su UploadLayoutManager per gestire altri tipi di file o nuovi layout.<\/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\">In chiusura<\/h3>\n<div class=\"siteorigin-widget-tinymce textwidget\">\n\t<p>Immagina dei caricatori di immagini costruiti appositamente per ogni scenario, ma che contengano solo alcuni semplici componenti fatti di markup di presentazione.  Ognuno di essi pu\u00f2 utilizzare la stessa funzionalit\u00e0 di caricamento, se ha senso, ma con una presentazione totalmente diversa. Oppure capovolgere l'idea, utilizzando le stesse immagini del caricatore ma con interfacce API completamente diverse.<\/p>\n<p>In quali altri modi potresti utilizzare questi componenti fondamentali? Quali altri uploader vorresti costruire? Il cielo \u00e8 il limite se ti prendi il tempo di costruire componenti riutilizzabili.<\/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\tTrova il post originale su 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\tIn primo piano nella newsletter React numero #130\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\tIn primo piano nel numero di React Status #102\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 was recently featured in the Eventbrite Engineering blog and we&#8217;re very proud of the work he does there. The Event Creation team at Eventbrite needed a React based image uploader that would provide flexibility while presenting a straightforward user interface. The image uploader components ought to work in a variety of scenarios as reusable [&hellip;]<\/p>\n","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\/it\/wp-json\/wp\/v2\/posts\/10330","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/comments?post=10330"}],"version-history":[{"count":12,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/posts\/10330\/revisions"}],"predecessor-version":[{"id":21449,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/posts\/10330\/revisions\/21449"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/media\/13450"}],"wp:attachment":[{"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/media?parent=10330"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/categories?post=10330"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/carterembry.com\/it\/wp-json\/wp\/v2\/tags?post=10330"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}