{"id":28255,"date":"2022-07-20T08:43:03","date_gmt":"2022-07-20T08:43:03","guid":{"rendered":"http:\/\/interactive-product-gallery-with-cloudinary"},"modified":"2022-07-20T08:43:03","modified_gmt":"2022-07-20T08:43:03","slug":"interactive-product-gallery-with-cloudinary","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/","title":{"rendered":"Interactive Product Gallery with Cloudinary"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>They say a picture speaks a thousand words. What if you needed the picture to speak more words than that? More words are certainly required to convince consumers to part with their hard-earned money, right? Adding interactivity to the images provides a more profound experience for the customer, making the product image almost tangible. In this tutorial, I will show you how to implement an in-house interactive media gallery with minimal fuss using <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a>.<\/p>\n<p>In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the <a href=\"https:\/\/cloudinary.com\/documentation\/product_gallery\">Cloudinary product gallery widget<\/a>.<\/p>\n<p>Here is a <a href=\"https:\/\/codesandbox.io\/s\/cloudinary-product-gallery-widget-8v8bni\">link<\/a> to the demo CodeSandbox.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/cloudinary-product-gallery-widget-8v8bni?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"cloudinary-product-gallery-widget-8v8bni\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Project Setup<\/h2>\n<p>Create a new <a href=\"https:\/\/reactjs.org\/\">React<\/a> app using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npx create-react-app cloudinary_product_gallery\n<\/code><\/span><\/pre>\n<p>Next, add the project dependencies using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">antd<\/span> <span class=\"hljs-keyword\">@ant-design<\/span>\/icons axios react-router-dom\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>For UI components in our application, we will use <a href=\"https:\/\/ant.design\/\">antd<\/a> while <a href=\"https:\/\/axios-http.com\/docs\/intro\">Axios<\/a> will be used for uploading media files to our Cloudinary store. <a href=\"https:\/\/v5.reactrouter.com\/web\/guides\/quick-start\">React router<\/a> will be used to transition between pages.<\/p>\n<p>Next, we need to import the antd CSS. To do this, open the <code>src\/App.css<\/code> file and edit its content to match the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-keyword\">@import<\/span> <span class=\"hljs-string\">\"~antd\/dist\/antd.css\"<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Set up Cloudinary<\/h2>\n<p>To use Cloudinary\u2019s provisioned services, you need to first sign up for a <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">free Cloudinary account<\/a> if you don\u2019t have one already. Important details are displayed on your account\u2019s Management Console (aka Dashboard): your cloud name, API key, etc.<\/p>\n<p>For this tutorial, we will be sending images to Cloudinary via unsigned POST requests. To do this, we need our account cloud name and an unsigned <a href=\"https:\/\/cloudinary.com\/documentation\/upload_presets\">upload preset<\/a>. To create one, select <strong>Settings<\/strong> &gt; <strong>Upload<\/strong> in the Management Console and then scroll to the <strong>Upload presets<\/strong> section. Create a new upload preset by clicking <strong>Add upload preset<\/strong> at the bottom of the upload preset list. In the displayed form, ensure the <strong>Signing Mode<\/strong> is set to <strong>Unsigned<\/strong>, as shown below.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_FAAA7EF3FF5AC52D70D82A5376F3814CA53497BB52251D0A03B45AF8654FCB9C_1657362305429_CleanShot+2022-07-09+at+11.24.142x.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"838\"\/><\/p>\n<p>Click <strong>Save<\/strong> to complete the upload preset definition, then copy the preset name displayed on the Upload Settings page.<\/p>\n<p>Next, let\u2019s create environment variables to hold the details of our Cloudinary account. Create a new file called .env at the root of your project and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">REACT_APP_CLOUD_NAME = <span class=\"hljs-string\">\"INSERT YOUR CLOUD NAME HERE\"<\/span>;\nREACT_APP_UPLOAD_PRESET = <span class=\"hljs-string\">\"INSERT YOUR UNSIGNED UPLOAD PRESET KEY HERE\"<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will be used as a default when the project is set up on another system. To update your local environment, create a copy of the <code>.env<\/code> file using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cp<\/span> <span class=\"hljs-selector-class\">.env<\/span> <span class=\"hljs-selector-class\">.env<\/span><span class=\"hljs-selector-class\">.local<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>By default, this local file resides in the <code>.gitignore<\/code> folder, mitigating the security risk of inadvertently exposing secret credentials to the public. You can update the <code>.env.local<\/code> file with your Cloudinary credentials.<\/p>\n<p>In the <code>src<\/code> directory of the project, create a new folder named <code>cloudinary<\/code>. This folder will hold the Cloudinary-related helper classes we will need in our components. In the <code>cloudinary<\/code> folder, create a new file called <code>cloudinaryConfig.js<\/code>. This file will give access to the environment variables and prevent repeated <code>process.env.<\/code> calls throughout the project. Add the following to the <code>cloudinaryConfig.js<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> cloudName = process.env.REACT_APP_CLOUD_NAME;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> uploadPreset = process.env.REACT_APP_UPLOAD_PRESET;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> defaultUploadTag = <span class=\"hljs-string\">\"cloudinary_interactive_gallery\"<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The Cloudinary gallery widget relies on the <a href=\"https:\/\/cloudinary.com\/documentation\/advanced_url_delivery_options#client_side_asset_lists\">Client-side asset lists<\/a> feature to retrieve the list of images (or videos) with the specified tag. To ensure that this feature is available on your Cloudinary account, you must ensure that the Resource list option is enabled. By default, the list delivery type is restricted. To enable it, open the <strong>Security settings<\/strong> in your Management console and clear <strong>the Resource lis<\/strong>t item under <strong>Restricted media types<\/strong>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_FAAA7EF3FF5AC52D70D82A5376F3814CA53497BB52251D0A03B45AF8654FCB9C_1657362225512_CleanShot+2022-07-09+at+11.22.452x.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"992\"\/><\/p>\n<p>With these in place, we can write the helper function to upload media files to Cloudinary. In the cloudinary folder, create a new file named <code>cloudinaryHelper.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> axios <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"axios\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { cloudName, defaultUploadTag, uploadPreset } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/cloudinaryConfig\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> upload = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ file, fileType, successCallback }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> url = <span class=\"hljs-string\">`https:\/\/api.cloudinary.com\/v1_1\/<span class=\"hljs-subst\">${cloudName}<\/span>\/<span class=\"hljs-subst\">${fileType}<\/span>\/upload`<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">new<\/span> FormData();\n  data.append(<span class=\"hljs-string\">\"file\"<\/span>, file);\n  data.append(<span class=\"hljs-string\">\"upload_preset\"<\/span>, uploadPreset);\n  data.append(<span class=\"hljs-string\">\"tags\"<\/span>, defaultUploadTag);\n  axios\n    .post(url, data, {\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"multipart\/form-data\"<\/span>,\n      },\n    })\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response<\/span>) =&gt;<\/span> successCallback(response.data));\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>upload<\/code> function is used to upload files to Cloudinary using the previously defined upload preset and upload tag. The function has one argument, which is an object containing the file to be uploaded, the file type (for this tutorial, it could be either image or video), and a function to be called upon receipt of a successful response from the Cloudinary API.<\/p>\n<h2>Add Gallery Context<\/h2>\n<p>The functionality for the gallery widget is delivered via CDN and instantiated in the <code>index.js<\/code> file. However, we need access to the instantiated object elsewhere in our application to update the widget on successful upload. To avoid prop drilling, we\u2019ll use React <a href=\"https:\/\/reactjs.org\/docs\/context.html\">Context<\/a> to pass this object to any component where it may be required. In the <code>src<\/code> folder, create a new folder named <code>context<\/code>. In the <code>src\/context<\/code> folder, create a new file named <code>GalleryContext.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { createContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> GalleryContext = createContext(<span class=\"hljs-literal\">null<\/span>);\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> GalleryContext;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With our context in place, we can instantiate the widget and update the context value.<\/p>\n<h2>Instantiate Gallery Widget<\/h2>\n<p>Open the <code>public\/index.html<\/code> file and add the following <code>&lt;script&gt;<\/code> tag in the head of the HTML file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>\n  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/product-gallery.cloudinary.com\/all.js\"<\/span>\n  <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\/javascript\"<\/span>\n&gt;<\/span><span class=\"actionscript\">\n  {<span class=\"hljs-string\">\" \"<\/span>}\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, update the <code>src\/index.js<\/code> file to match the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> ReactDOM <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-dom\/client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\".\/index.css\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> App <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/App\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { defaultUploadTag, cloudName } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/cloudinary\/cloudinaryConfig\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> GalleryContext <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/context\/GalleryContext\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { BrowserRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n\n<span class=\"hljs-comment\">\/* eslint-disable *\/<\/span>\n<span class=\"hljs-keyword\">const<\/span> myGallery = cloudinary.galleryWidget({\n  <span class=\"hljs-attr\">container<\/span>: <span class=\"hljs-string\">\"#gallery\"<\/span>,\n  <span class=\"hljs-attr\">cloudName<\/span>: cloudName,\n  <span class=\"hljs-attr\">mediaAssets<\/span>: &#91;\n    { <span class=\"hljs-attr\">tag<\/span>: defaultUploadTag }, <span class=\"hljs-comment\">\/\/ by default mediaType: \"image\"<\/span>\n    { <span class=\"hljs-attr\">tag<\/span>: defaultUploadTag, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"video\"<\/span> },\n  ],\n});\n\n<span class=\"hljs-keyword\">const<\/span> root = ReactDOM.createRoot(<span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"root\"<\/span>));\nroot.render(\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">React.StrictMode<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">BrowserRouter<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">GalleryContext.Provider<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{myGallery}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">App<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">GalleryContext.Provider<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">BrowserRouter<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">React.StrictMode<\/span>&gt;<\/span><\/span>\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here we initialize the product gallery widget with the <code>cloudinary.galleryWidget<\/code> method call. We pass the initialization options, including the cloud name and the tags of the media assets we wish to retrieve. We also specify a key named <code>container<\/code>. This is the id of the <code>div<\/code> element in which the galley will be rendered. The instantiated object is then passed to the <code>GalleryContext<\/code> provider via the <code>value<\/code> prop.<\/p>\n<blockquote>\n<p>We used the eslint-disable annotation to avoid eslint throwing an undefined variable exception.<\/p>\n<\/blockquote>\n<h2>Add Components<\/h2>\n<p>With the gallery widget instantiated and the object made available via Context, let\u2019s create two components &#8211; one for uploading files and the other for rendering the gallery.<\/p>\n<p>In the <code>src<\/code> folder, create a new folder named <code>components<\/code>. Next, in the <code>src\/components<\/code> folder, create a new file called <code>MediaUpload.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Button, Card, message, Upload } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"antd\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { UploadOutlined } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@ant-design\/icons\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useNavigate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { upload } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/cloudinary\/cloudinaryHelper\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { defaultUploadTag } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/cloudinary\/cloudinaryConfig\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> GalleryContext <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/context\/GalleryContext\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> MediaUpload = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;isUploading, setIsUploading] = useState(<span class=\"hljs-literal\">false<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;selectedFiles, setSelectedFiles] = useState(&#91;]);\n  <span class=\"hljs-keyword\">const<\/span> navigate = useNavigate();\n  <span class=\"hljs-keyword\">const<\/span> myGallery = useContext(GalleryContext);\n\n  <span class=\"hljs-keyword\">const<\/span> uploadSelection = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (selectedFiles.length === <span class=\"hljs-number\">0<\/span>) {\n      message.error(<span class=\"hljs-string\">\"You need to upload a media file first\"<\/span>);\n    } <span class=\"hljs-keyword\">else<\/span> {\n      setIsUploading(<span class=\"hljs-literal\">true<\/span>);\n      selectedFiles.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">file, index<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-keyword\">const<\/span> fileType = file&#91;<span class=\"hljs-string\">\"type\"<\/span>].split(<span class=\"hljs-string\">\"\/\"<\/span>)&#91;<span class=\"hljs-number\">0<\/span>];\n        upload({\n          file,\n          fileType,\n          <span class=\"hljs-attr\">successCallback<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n            <span class=\"hljs-keyword\">if<\/span> (index === selectedFiles.length - <span class=\"hljs-number\">1<\/span>) {\n              message.success(<span class=\"hljs-string\">\"Images uploaded successfully\"<\/span>);\n              myGallery.update({\n                <span class=\"hljs-attr\">mediaAssets<\/span>: &#91;\n                  { <span class=\"hljs-attr\">tag<\/span>: defaultUploadTag },\n                  { <span class=\"hljs-attr\">tag<\/span>: defaultUploadTag, <span class=\"hljs-attr\">mediaType<\/span>: <span class=\"hljs-string\">\"video\"<\/span> },\n                ],\n              });\n              setIsUploading(<span class=\"hljs-literal\">false<\/span>);\n              navigate(<span class=\"hljs-string\">\"\/gallery\"<\/span>);\n            }\n          },\n        });\n      });\n    }\n  };\n\n  <span class=\"hljs-keyword\">const<\/span> props = {\n    <span class=\"hljs-attr\">multiple<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-attr\">onRemove<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">file<\/span>) =&gt;<\/span> {\n      setSelectedFiles(<span class=\"hljs-function\">(<span class=\"hljs-params\">currentSelection<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-keyword\">const<\/span> newSelection = currentSelection.slice();\n        <span class=\"hljs-keyword\">const<\/span> fileIndex = currentSelection.indexOf(file);\n        newSelection.splice(fileIndex, <span class=\"hljs-number\">1<\/span>);\n        <span class=\"hljs-keyword\">return<\/span> newSelection;\n      });\n    },\n    <span class=\"hljs-attr\">beforeUpload<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">file<\/span>) =&gt;<\/span> {\n      setSelectedFiles(<span class=\"hljs-function\">(<span class=\"hljs-params\">currentSelection<\/span>) =&gt;<\/span> &#91;...currentSelection, file]);\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\n    },\n    <span class=\"hljs-attr\">showUploadList<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Card<\/span>\n      <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">margin:<\/span> \"<span class=\"hljs-attr\">auto<\/span>\", <span class=\"hljs-attr\">width:<\/span> \"<span class=\"hljs-attr\">50<\/span>%\" }}\n      <span class=\"hljs-attr\">actions<\/span>=<span class=\"hljs-string\">{&#91;<\/span>\n        &lt;<span class=\"hljs-attr\">Button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"primary\"<\/span> <span class=\"hljs-attr\">loading<\/span>=<span class=\"hljs-string\">{isUploading}<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{uploadSelection}<\/span>&gt;<\/span>\n          Submit\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>,\n      ]}\n    &gt;\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Upload.Dragger<\/span> {<span class=\"hljs-attr\">...props<\/span>}&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"ant-upload-drag-icon\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadOutlined<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"ant-upload-text\"<\/span>&gt;<\/span>Click to Upload Files<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Upload.Dragger<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Card<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> MediaUpload;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This component renders a simple drag and drop form using the antd <code>Upload<\/code> component. A submit button is also rendered, which calls the <code>uploadSelection<\/code> function when clicked.<\/p>\n<p>If at least one file is selected, the function iterates through the <code>selectedFiles<\/code> state variable (updated when files are selected or removed via the <code>Upload<\/code> component). For each file, it calls the <code>upload<\/code> function we declared earlier.<\/p>\n<p>Once the files have been uploaded, a success message is displayed, and the gallery widget (retrieved via the <code>useContext<\/code> call) is used to update the widget. Finally, the application redirects to the <code>Gallery<\/code> component, which we will build next.<\/p>\n<p>In the <code>src\/components<\/code> folder, create a new file named <code>Gallery.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useEffect, useContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> GalleryContext <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/context\/GalleryContext\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Gallery = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> gallery = useContext(GalleryContext);\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    gallery.render();\n  }, &#91;]);\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Gallery<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"gallery\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Gallery;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This component retrieves the gallery widget from <code>GalleryContext<\/code> and declares the target container &#8211; an empty <code>div<\/code> with id gallery. An <code>useEffect<\/code> call is used to render the gallery on page load.<\/p>\n<h2>Add Navigation<\/h2>\n<p>We have our components, but we need to implement the routing between them. We also need a component to help the user trigger the navigation process where necessary.<\/p>\n<p>We\u2019ll start by adding a menu. In the <code>src\/components<\/code> folder, create a new file named <code>Menu.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { Menu <span class=\"hljs-keyword\">as<\/span> AntDMenu } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"antd\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Link } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { PictureOutlined, UploadOutlined } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@ant-design\/icons\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Menu = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;currentlySelected, setCurrentlySelected] = useState(<span class=\"hljs-string\">\"upload\"<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> handleMenuSelection = <span class=\"hljs-function\">(<span class=\"hljs-params\">e<\/span>) =&gt;<\/span> {\n    setCurrentlySelected(e.key);\n  };\n\n  <span class=\"hljs-keyword\">const<\/span> items = &#91;\n    {\n      <span class=\"hljs-attr\">label<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">\"\/gallery\"<\/span>&gt;<\/span>Gallery<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span>,\n      <span class=\"hljs-attr\">key<\/span>: <span class=\"hljs-string\">\"gallery\"<\/span>,\n      <span class=\"hljs-attr\">icon<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PictureOutlined<\/span> \/&gt;<\/span><\/span>,\n    },\n    {\n      <span class=\"hljs-attr\">label<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>Upload Media<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span>,\n      <span class=\"hljs-attr\">key<\/span>: <span class=\"hljs-string\">\"upload\"<\/span>,\n      <span class=\"hljs-attr\">icon<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadOutlined<\/span> \/&gt;<\/span><\/span>,\n    },\n  ];\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AntDMenu<\/span>\n      <span class=\"hljs-attr\">mode<\/span>=<span class=\"hljs-string\">\"horizontal\"<\/span>\n      <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{handleMenuSelection}<\/span>\n      <span class=\"hljs-attr\">selectedKeys<\/span>=<span class=\"hljs-string\">{&#91;currentlySelected]}<\/span>\n      <span class=\"hljs-attr\">items<\/span>=<span class=\"hljs-string\">{items}<\/span>\n    \/&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Menu;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This menu provides two items &#8211; one for the gallery and the other for the upload form. Using the <code>Link<\/code> component provided by react-router-dom, we specify the path to which the application should redirect when an item is clicked.<\/p>\n<p>Next, in the <code>src<\/code> folder, create a new file named <code>routes.js<\/code> and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Gallery <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/Gallery\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> MediaUpload <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/MediaUpload\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> routes = &#91;\n  {\n    <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">\"\/\"<\/span>,\n    <span class=\"hljs-attr\">element<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">MediaUpload<\/span> \/&gt;<\/span><\/span>,\n  },\n  {\n    <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">\"\/gallery\"<\/span>,\n    <span class=\"hljs-attr\">element<\/span>: <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Gallery<\/span> \/&gt;<\/span><\/span>,\n  },\n];\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> routes;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here we specify the component to be rendered for a given path.<\/p>\n<h2>Update App.js<\/h2>\n<p>Finally, update <code>src\/App.js<\/code> to match the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\".\/App.css\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRoutes } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> routes <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/routes\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Menu <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/Menu\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Col, Row } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"antd\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> App = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRoutes(routes);\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">margin:<\/span> \"<span class=\"hljs-attr\">1<\/span>%\" }}&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Menu<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">textAlign:<\/span> \"<span class=\"hljs-attr\">center<\/span>\" }}&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Row<\/span> <span class=\"hljs-attr\">justify<\/span>=<span class=\"hljs-string\">\"center\"<\/span> <span class=\"hljs-attr\">align<\/span>=<span class=\"hljs-string\">\"middle\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">textAlign:<\/span> \"<span class=\"hljs-attr\">center<\/span>\" }}&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Col<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">width:<\/span> \"<span class=\"hljs-attr\">100<\/span>%\", <span class=\"hljs-attr\">margin:<\/span> \"<span class=\"hljs-attr\">2<\/span>%\" }}&gt;<\/span>{router}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Col<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Row<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here we render the menu along with the content rendered by the router (as determined by the current path).<\/p>\n<p>With everything in place, run your application using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm start\n<\/code><\/span><\/pre>\n<p>By default, the application will be available at <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a>. The final result will look like the GIF shown below.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_FAAA7EF3FF5AC52D70D82A5376F3814CA53497BB52251D0A03B45AF8654FCB9C_1657362142365_CleanShot+2022-07-09+at+11.20.57.gif\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"800\" height=\"478\"\/><\/p>\n<p>Find the complete project <a href=\"https:\/\/github.com\/ifeoma-imoh\/cloudinary-product-gallery-widget\">here<\/a> on GitHub.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we looked at how to build an interactive product gallery using Cloudinary. Using Cloudinary, not only can we deliver a seamless storage and retrieval process for images, we are also able to render an interactive, aesthetically pleasing gallery without worrying about styling, functionality, and the likes (the Gallery component is less than 30 lines long).<\/p>\n<p>For this tutorial, we rendered only images and videos. However, the widget can also render 360 spin sets and 3D models, which you can read more about in the Cloudinary <a href=\"https:\/\/cloudinary.com\/documentation\/product_gallery#360_spin_sets\">guide<\/a>.\nWith all these, your application guarantees a seamless experience that speaks much more than a static image.<\/p>\n<p><strong>Resources you may find helpful:<\/strong><\/p>\n<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/product_gallery\">Cloudinary Product Gallery<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/upload_images#uploading_with_a_direct_call_to_the_rest_api\">Uploading with a Direct Call to the Rest API<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/ant.design\/\">Ant Design documentation<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28256,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[399,134,370,246,371,303],"class_list":["post-28255","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ui-widget","tag-guest-post","tag-image","tag-react","tag-under-review","tag-video"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Interactive Product Gallery with Cloudinary<\/title>\n<meta name=\"description\" content=\"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Interactive Product Gallery with Cloudinary\" \/>\n<meta property=\"og:description\" content=\"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-07-20T08:43:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"1280\" \/>\n\t<meta property=\"og:image:height\" content=\"720\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Interactive Product Gallery with Cloudinary\",\"datePublished\":\"2022-07-20T08:43:03+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\"},\"wordCount\":5,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA\",\"keywords\":[\"(UI) Widget\",\"Guest Post\",\"Image\",\"React\",\"Under Review\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\",\"name\":\"Interactive Product Gallery with Cloudinary\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA\",\"datePublished\":\"2022-07-20T08:43:03+00:00\",\"description\":\"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA\",\"width\":1280,\"height\":720},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interactive Product Gallery with Cloudinary\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Interactive Product Gallery with Cloudinary","description":"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/","og_locale":"en_US","og_type":"article","og_title":"Interactive Product Gallery with Cloudinary","og_description":"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-07-20T08:43:03+00:00","og_image":[{"width":1280,"height":720,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/"},"author":{"name":"","@id":""},"headline":"Interactive Product Gallery with Cloudinary","datePublished":"2022-07-20T08:43:03+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/"},"wordCount":5,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","keywords":["(UI) Widget","Guest Post","Image","React","Under Review","Video"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/","name":"Interactive Product Gallery with Cloudinary","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","datePublished":"2022-07-20T08:43:03+00:00","description":"In this tutorial, we will build the gallery management feature for an e-commerce web application. Using the application, you can upload images and videos of a product. Upon successful upload, you can view the items rendered using the Cloudinary product gallery widget","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","width":1280,"height":720},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/interactive-product-gallery-with-cloudinary\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Interactive Product Gallery with Cloudinary"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925012\/Web_Assets\/blog\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8\/c857d7e953515ff0cd88801487286ab76dd0deac-1280x720-1_28256c37c8.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28255","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28255"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28255\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28256"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28255"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28255"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28255"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}