{"id":21677,"date":"2018-02-12T19:18:31","date_gmt":"2018-02-12T19:18:31","guid":{"rendered":"http:\/\/improving_instagram_web_new_features_for_a_better_user_experience"},"modified":"2025-09-21T04:44:57","modified_gmt":"2025-09-21T11:44:57","slug":"improving_instagram_web_new_features_for_a_better_user_experience","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","title":{"rendered":"Improving Instagram Web With Progressive Image Loading"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>My work demands that I stay away from my phone and mobile notifications in order to be as  productive as possible each day. It\u2019s not unusual to find me at my desk for a total 12 hours a day (I work remotely), with four hours going to browsing the internet.<\/p>\n<p>As I <em>sniff around<\/em>, <a href=\"https:\/\/www.instagram.com\/\">Instagram Web<\/a> is open constantly, with a visit rate of 2.4 percent of my daily hours. I don\u2019t think this is just me, because in the U.S. alone, <a href=\"https:\/\/www.statista.com\/statistics\/272933\/distribution-of-instagram-traffic-by-country\/\">17 percent of Instagram traffic comes from desktops<\/a>.<\/p>\n<p>As an engineer, I have a tendency to try to improve a tool (productivity or fun) I use everyday. For me, it is Instagram. So, I assembled a list of possible <strong>Instagram Web<\/strong> improvements I can work on, built a simple Instagram clone, then improved on those points. In this article, I\u2019ll talk more about what I\u2019ve done, and you might even see more room for improvements.<\/p>\n<h2>Needed Changes<\/h2>\n<p>Maybe the Instagram team thought of the points I\u2019m about to discuss, and had their reasons for not making these types of changes. Or, maybe it never occurred to them to do these things. Who knows?  Here\u2019s list the features I wish I had every time I access Instagram from my computer (<strong>NOT<\/strong> my phone):<\/p>\n<ol>\n<li>\n<p><strong>Scroll to Play<\/strong>: I keep asking myself why this is not a feature on the web, yet it is on mobile. After a long session using scrolling infinitely on Instagram mobile, I forget that you have to click a video on Instagram Web before it plays. Most time, I just wait thinking my network has slowed down again until I snap back. What exact reason is Instagram Web refusing to play videos? Who knows?<\/p>\n<p><video width=\"320\" height=\"180\" autoplay loop muted=\"muted\" poster=\"https:\/\/cloudinary-res.cloudinary.com\/video\/upload\/w_400,c_fill\/Web_scroll.jpg\"><source type=\"video\/mp4\" src=\"https:\/\/cloudinary-res.cloudinary.com\/video\/upload\/w_400,c_fill\/Web_scroll.mp4\"> <\/video><\/p>\n<\/li>\n<li>\n<p><strong>Hover to Mute and Unmute<\/strong>: Yeah, I can afford a hover you know? I am on a desktop, not a mobile phone. A hover event is my well-earned right for buying a large computer. Why not utilize it, Instagram Web? Oh yes, I can click to play and pause \u2014 I know that. But what if I want the videos to play without a sound? Hover could do that well.<\/p>\n<\/li>\n<li>\n<p><strong>Progressive Image Loading<\/strong>: We all know that images are optimized and the web app probably uses <a href=\"https:\/\/css-tricks.com\/responsive-images-youre-just-changing-resolutions-use-srcset\/\">resolution switching<\/a> to serve the best image resolution for a browser view port. I live in Africa where <strong>slow<\/strong> 3G is a norm, so instead of slashing that grey background at users on a poor network, maybe you can borrow some ideas from Medium and load the images progressively.<\/p>\n<\/li>\n<\/ol>\n<h2>How I Intend to Make Improvements<\/h2>\n<p>Of course I don\u2019t have a lot of time, so I can\u2019t imagine building some of these features from scratch. Oh, unless you\u2019re paying me\u2026then we can talk. What I intend to do is use a third-party service &#8211; Cloudinary &#8211; that solves most of these problems. Cloudinary is an end-to-end, cloud-based media management solution. It offers media (images and videos) storage, <a href=\"https:\/\/cloudinary.com\/documentation\/image_transformations\">transformation<\/a> and delivery.<\/p>\n<p>Here is a list of where Cloudinary could help improve the app:<\/p>\n<ul>\n<li>Video\/image delivery and upload<\/li>\n<li>Cropping and padding transformation<\/li>\n<li>Width and height transformation<\/li>\n<li>Progressive loading<\/li>\n<li>Optimization<\/li>\n<li>Media storage<\/li>\n<li>Video Player with <strong>Scroll to Play<\/strong>\n<\/li>\n<\/ul>\n<h2>Setting Up the App<\/h2>\n<p>Instagram Web was built with React, so, I am going to prove that the limitations are not tool-specific by using React in the examples. Run the following command to create a React app:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Install CRA<\/span>\nnpm install -g create-react-app\n\n<span class=\"hljs-comment\"># Create a New App<\/span>\ncreate-react-app &lt;app-name&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Persisting Payloads with a Simple API Server<\/h2>\n<p>We need a simple data persistence mechanism to store a list of posts. Each post would have the basic requirements, including <strong>nickname<\/strong>, <strong>avatar<\/strong>, <strong>caption<\/strong> and <strong>post <a href=\"https:\/\/cloudinary.com\/glossary\/media-url\">media ur<\/a>l<\/strong>. We can use file storage on a server to keep the posts as JSON files and read and write from them.<\/p>\n<p><strong>Server Requirements\/Dependencies and Configurations<\/strong><\/p>\n<p>To have the appropriate server running, you would need the following:<\/p>\n<ul>\n<li>\n<strong>node<\/strong>: Almighty JS on the server<\/li>\n<li>\n<strong>express<\/strong>: HTTP routing framework for Node<\/li>\n<li>\n<strong>body-parser<\/strong>: A middleware to parse HTTP body and attach the content to the <code>req<\/code> object<\/li>\n<li>\n<strong>cors<\/strong>: express middleware to enable CORS<\/li>\n<li>\n<strong>low<\/strong>:  a small local JSON database powered by Lodash<\/li>\n<li>\n<strong>uuid<\/strong>:  Generates UUID to serve as unique IDs for each posts<\/li>\n<\/ul>\n<p>Install the server dependencies by running:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install express body-parser cors lowdb uuid\n<\/code><\/span><\/pre>\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>You need to have node installed before running the above command<\/p><\/div>\n<p>Next, create a <code>server.js<\/code> file on the root of the React project. Then import the dependencies and configure express:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Import Dependencies<\/span>\n<span class=\"hljs-keyword\">const<\/span> Express = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'express'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> bodyParser = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'body-parser'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> cors = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'cors'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> low = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'lowdb'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> FileSync = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'lowdb\/adapters\/FileSync'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> uuid = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'uuid\/v4'<\/span>);\n\n<span class=\"hljs-comment\">\/\/ Create an Express app<\/span>\n<span class=\"hljs-keyword\">const<\/span> app = Express();\n\n<span class=\"hljs-comment\">\/\/ Configure middleware<\/span>\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">cors<\/span>());\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">urlencoded<\/span>({ <span class=\"hljs-title\">extended<\/span>: <span class=\"hljs-title\">false<\/span> }));\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">json<\/span>());\n\n<span class=\"hljs-comment\">\/\/ Configure database and use a file adapter<\/span>\n<span class=\"hljs-keyword\">const<\/span> adapter = <span class=\"hljs-keyword\">new<\/span> FileSync(<span class=\"hljs-string\">'db.json'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> db = low(adapter);\n\n<span class=\"hljs-comment\">\/\/ Choose a port<\/span>\napp.set(<span class=\"hljs-string\">'port'<\/span>, <span class=\"hljs-number\">8070<\/span>);\n\n<span class=\"hljs-comment\">\/*\n*\n* &#91; R O U T E S    H E R E]\n*\n*\/<\/span>\n\n<span class=\"hljs-comment\">\/\/ Listen to the chosen port<\/span>\napp.listen(app.get(<span class=\"hljs-string\">'port'<\/span>), _ =&gt; console.log(<span class=\"hljs-string\">'App at '<\/span> + app.get(<span class=\"hljs-string\">'port'<\/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\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You can start running the app now:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">node<\/span> <span class=\"hljs-selector-tag\">server<\/span><span class=\"hljs-selector-class\">.js<\/span>\n\n# <span class=\"hljs-selector-tag\">OR<\/span>\n\n<span class=\"hljs-selector-tag\">nodemon<\/span> <span class=\"hljs-selector-tag\">server<\/span><span class=\"hljs-selector-class\">.js<\/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\">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><strong>HTTP Routes<\/strong><\/p>\n<p>The React app is going to make HTTP requests to this server and point to a route while doing so. We need to define these routes and implement the behavior when they are visited. The first route creates a <strong>single<\/strong> post:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app.set(<span class=\"hljs-string\">'port'<\/span>, <span class=\"hljs-number\">8070<\/span>);\n<span class=\"hljs-comment\">\/\/<\/span>\napp.post(<span class=\"hljs-string\">'\/posts'<\/span>, (req, res) =&gt; {\n  <span class=\"hljs-comment\">\/\/ Assemble data from the requesting client<\/span>\n  <span class=\"hljs-comment\">\/\/ Also assign an id and a creation time<\/span>\n  <span class=\"hljs-keyword\">const<\/span> post = <span class=\"hljs-built_in\">Object<\/span>.assign({}, req.body, {\n    <span class=\"hljs-attr\">id<\/span>: uuid(),\n    <span class=\"hljs-attr\">created_at<\/span>: <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>()\n  });\n  \n  <span class=\"hljs-comment\">\/\/ Create post using `low`<\/span>\n  db\n    .get(<span class=\"hljs-string\">'posts'<\/span>)\n    .push(post)\n    .write();\n  <span class=\"hljs-comment\">\/\/ Respond with the last post that was created<\/span>\n  <span class=\"hljs-keyword\">const<\/span> newPost = db\n    .get(<span class=\"hljs-string\">'posts'<\/span>)\n    .last()\n    .value();\n  res.json(newPost);\n});\n<span class=\"hljs-comment\">\/\/<\/span>\napp.listen(app.get(<span class=\"hljs-string\">'port'<\/span>), _ =&gt; <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'App at '<\/span> + app.get(<span class=\"hljs-string\">'port'<\/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\">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>I am using the express instance to handle a post request using the <code>post<\/code> instance method. When the request comes in, and the URL matches <code>http:\/\/localhost:8070\/posts<\/code>, the callback function passed to this instance method is called. The route just assembles the data received from the client, stores it and sends it back to the client as a confirmation that the persisting process was successful.<\/p>\n<p>Almost the same pattern goes for fetching all the available posts:<\/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\">app.post(<span class=\"hljs-string\">'\/posts'<\/span>, (req, res) =&gt; {});\n<span class=\"hljs-comment\">\/\/<\/span>\napp.get(<span class=\"hljs-string\">'\/posts'<\/span>, (req, res) =&gt; {\n  <span class=\"hljs-comment\">\/\/ Fetch a post based on the<\/span>\n  <span class=\"hljs-comment\">\/\/ offset and limit<\/span>\n  <span class=\"hljs-keyword\">const<\/span> posts = db\n    .get(<span class=\"hljs-string\">'posts'<\/span>)\n    .orderBy(&#91;<span class=\"hljs-string\">'created_at'<\/span>], &#91;<span class=\"hljs-string\">'desc'<\/span>])\n    .slice(<span class=\"hljs-built_in\">parseInt<\/span>(req.query.offset) - <span class=\"hljs-number\">1<\/span>)\n    .take(<span class=\"hljs-built_in\">parseInt<\/span>(req.query.limit))\n    .value();\n  \n  <span class=\"hljs-comment\">\/\/ Get the total count<\/span>\n  <span class=\"hljs-keyword\">const<\/span> count = db.get(<span class=\"hljs-string\">'posts'<\/span>).value().length;\n  <span class=\"hljs-comment\">\/\/ Send a response<\/span>\n  res.json({ <span class=\"hljs-attr\">posts<\/span>: posts, <span class=\"hljs-attr\">count<\/span>: count });\n});\n<span class=\"hljs-comment\">\/\/ <\/span>\napp.listen(app.get(<span class=\"hljs-string\">'port'<\/span>), _ =&gt; <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'App at '<\/span> + app.get(<span class=\"hljs-string\">'port'<\/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>In addition to returning a <strong>queried<\/strong> list of posts, we also get the total number of posts and send it to the client. This would enable the client implement features like <strong>pagination<\/strong> or <strong>infinite scroll<\/strong>.<\/p>\n<h2>Getting Ready for the Client (React) App<\/h2>\n<p>Leave the React app running and watching for changes by running the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn start\n<\/code><\/span><\/pre>\n<p>Empty whatever is contained in the <code>.\/src\/App.js<\/code> file so we can start on a blank slate. Replace with:<\/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> React, { Component } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n  \n<span class=\"hljs-keyword\">import<\/span> Header <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/Header'<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">App<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Component<\/span> <\/span>{\n  render() {\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\">className<\/span>=<span class=\"hljs-string\">\"App\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Header<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\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-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>We trimmed it down to just showing a nav header. Create the header by adding a folder named <code>Header<\/code> to <code>src<\/code> and a file in the folder named <code>index.js<\/code>:<\/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> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/Header.css'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Header = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Nav\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Nav-menus\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Nav-brand\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Nav-brand-logo\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n          Instagram\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/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>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span><\/span>\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Header;\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>Nothing functional, just a fancy header when you add <code>Header.css<\/code> and image sprite from <a href=\"https:\/\/github.com\/christiannwamba\/cl-instagram\/commit\/d7d6917479bafaf0e4828df839e0e37d34204a7e#diff-14b1e33d5bf5649597cdc0e4f684dadd\">this commit history<\/a>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_700,c_fill\/dpr_auto\/_z3pe7n.png\" alt=\"Instagram\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1400\" height=\"979\"\/><\/p>\n<h2>Showing a Post<\/h2>\n<p>Just like the header, you need a few JSX and styles to show a post prototype on the screen.  Add another component <code>Post<\/code> with a root index and add the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" 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> <span class=\"hljs-string\">'.\/Post.css'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Post = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user-avatar\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n            <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"_rewi8\"<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/instagram.flos6-1.fna.fbcdn.net\/t51.2885-19\/s150x150\/14727482_199282753814164_8390284987160592384_a.jpg\"<\/span>\n          \/&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> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user-nickname\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>ogrant718<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/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>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-image\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-image-bg\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/instagram.flos6-1.fna.fbcdn.net\/t51.2885-15\/e35\/24845932_1757866441186546_5996861590417178624_n.jpg\"<\/span>\n          <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n        \/&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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-caption\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">strong<\/span>&gt;<\/span>ogrant718<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">strong<\/span>&gt;<\/span> Drops at midnight\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span>\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Post;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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><a href=\"https:\/\/github.com\/christiannwamba\/cl-instagram\/commit\/aea54fd490ef14a29f9297bf4bdd05749047a506\">This commit history<\/a> has the styles, so you can create <code>Post.css<\/code> in the same root as <code>index.js<\/code> and add the styles.<\/p>\n<p>Update the <code>App.js<\/code> component to include the new post component:<\/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-comment\">\/\/<\/span>\n<span class=\"hljs-keyword\">import<\/span> Post <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/Post'<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">App<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Component<\/span> <\/span>{\n  render() {\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\">className<\/span>=<span class=\"hljs-string\">\"App\"<\/span>&gt;<\/span>\n        ...\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"App-main\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Post<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\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-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>Save, and once your browser reloads, you should get the following result:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_700,c_fill\/dpr_auto\/ogrant.png\" alt=\"Instagram\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1400\" height=\"1071\"\/><\/p>\n<p>We can\u2019t have just a single post, we need a list of them. Let\u2019s get to that!<\/p>\n<h2>Render a List of Posts<\/h2>\n<p>We need to render a list of posts, and to do that we need another component to contain the Post component and then iterate over an array of data and display a Post component for each item in the array. Create another component Posts:<\/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> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Post <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/Post'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/Posts.css'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Posts = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Posts\"<\/span>&gt;<\/span>\n    {(&#91;1, 2, 3]).map(v =&gt; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Post<\/span> \/&gt;<\/span>)}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Posts;\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>There is no array of data coming from the server yet, so we just iterate through an array of static figures. Now you can replace the <code>Post<\/code> in <code>App<\/code> with <code>Posts<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" 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\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"App-main\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Posts<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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>And there, you have a list of Instagram posts:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_700,c_fill\/dpr_auto\/poja.png\" alt=\"Instagram\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1400\" height=\"1078\"\/><\/p>\n<h2>Connect Real Data<\/h2>\n<p>Rather than iterating over numbers, why not make a request to your server (which is running) and ask for a list of posts. I already populated the repo with a <a href=\"https:\/\/github.com\/christiannwamba\/cl-instagram\/commit\/98f206eadb87aa8ad59a4ff6258d143f2c5a6746\">list of posts<\/a>. Update the App component to request for them:<\/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-comment\">\/\/ Import axios<\/span>\n<span class=\"hljs-keyword\">import<\/span> axios <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'axios'<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">App<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Component<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>(props) {\n    <span class=\"hljs-keyword\">super<\/span>(props);\n    <span class=\"hljs-keyword\">this<\/span>.state = {\n      <span class=\"hljs-attr\">posts<\/span>: &#91;],\n      <span class=\"hljs-attr\">offset<\/span>: <span class=\"hljs-number\">1<\/span>,\n      <span class=\"hljs-attr\">limit<\/span>: <span class=\"hljs-number\">4<\/span>\n    };\n    <span class=\"hljs-keyword\">this<\/span>.baseUrl = <span class=\"hljs-string\">'http:\/\/localhost:8070\/posts'<\/span>;\n  }\n\n  <span class=\"hljs-keyword\">get<\/span> constructFetchUrl() {\n    <span class=\"hljs-keyword\">const<\/span> { offset, limit } = <span class=\"hljs-keyword\">this<\/span>.state;\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.baseUrl}<\/span>?offset=<span class=\"hljs-subst\">${offset}<\/span>&amp;limit=<span class=\"hljs-subst\">${limit}<\/span>`<\/span>;\n  }\n\n  componentDidMount() {\n    axios\n      .get(<span class=\"hljs-keyword\">this<\/span>.constructFetchUrl)\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ data }<\/span>) =&gt;<\/span> (<span class=\"hljs-keyword\">this<\/span>.setState({<span class=\"hljs-attr\">posts<\/span>: data})))\n      .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">err<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">console<\/span>.log(err));\n  }\n\n  render() {\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\">className<\/span>=<span class=\"hljs-string\">\"App\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Header<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"App-main\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Posts<\/span> <span class=\"hljs-attr\">posts<\/span>=<span class=\"hljs-string\">{this.state.posts}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\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-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>The Posts component now receives a state named props, which is populated after an ajax request. To make this request, you need to install <code>axios<\/code>, a HTTP library:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn add axios\n<\/code><\/span><\/pre>\n<p>Next, use the <code>componentDidMount<\/code> lifecycle method to hook-in and make the Ajax request once the React component is read. The <code>axios<\/code> request returns a promise, which, when resolved, has a payload of posts. You can update the state once the promise is resolved.<\/p>\n<p>You need to receive the posts in the <code>Posts<\/code> component and replace the array of static numbers with 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\">const<\/span> Posts = <span class=\"hljs-function\">(<span class=\"hljs-params\">{posts}<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Posts\"<\/span>&gt;<\/span>\n    {posts.map(post =&gt; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Post<\/span> {<span class=\"hljs-attr\">...post<\/span>} <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{post.id}<\/span>  \/&gt;<\/span>)}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n);\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>Then update the static JSX content in the <code>Post<\/code> component so it shows the actual data from the server:<\/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> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> isVideo = <span class=\"hljs-function\"><span class=\"hljs-params\">url<\/span> =&gt;<\/span> url.split(<span class=\"hljs-string\">'.'<\/span>)&#91;url.split(<span class=\"hljs-string\">'.'<\/span>).length - <span class=\"hljs-number\">1<\/span>] === <span class=\"hljs-string\">'mp4'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Post = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ user: { nickname, avatar }, post: { image, caption } }<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user-avatar\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{avatar}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{nickname}<\/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> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-user-nickname\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>{nickname}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/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>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-image\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-image-bg\"<\/span>&gt;<\/span>\n        {isVideo(image) ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n            <span class=\"hljs-attr\">playsInline<\/span>\n            <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">\"http:\/\/via.placeholder.com\/800x800\"<\/span>\n            <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n            <span class=\"hljs-attr\">controls<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image}<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span>\n          \/&gt;<\/span>\n        ) : (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image}<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n          \/&gt;<\/span>\n        )}\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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-caption\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">strong<\/span>&gt;<\/span>{nickname}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">strong<\/span>&gt;<\/span> {caption}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span>\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Post;\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>Remember Instagram supports two kinds of posts \u2014 images and videos. The <code>isVideo<\/code> method checks if the URL is a video and then uses the video tag to display it. For everything else, it uses the image tag.<\/p>\n<h2>Using the Cloudinary Video Player<\/h2>\n<p>Nothing is wrong with the HTML5 video player you saw above. But there will be a problem soon. For us to transform the videos to our taste, and have 100 percent control over the process, we need to use a third-party library. Cloudinary open sourced a video player recently that handles every video player problem you can think of. You can install the player via npm:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn add cloudinary-core cloudinary-video-player\n<\/code><\/span><\/pre>\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>cloudinary-video-player is the actual library for playing videos while cloudinary-core is the general Cloudinary JavaScript SDK for delivering media contents and transforming them.<\/p><\/div>\n<p>Import the libraries to the <code>Post<\/code> component:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" 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> cloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary-core'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'cloudinary-video-player'<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Video Player CSS<\/span>\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'..\/..\/node_modules\/cloudinary-video-player\/dist\/cld-video-player.min.css'<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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>Create an instance of the SDK and the video player and mount the video player instance on the <a href=\"https:\/\/cloudinary.com\/guides\/front-end-development\/html5-video-tag\">HTML5 video tag<\/a> we had using <code>ref<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Post<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Component<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>(props) {\n    <span class=\"hljs-keyword\">super<\/span>(props);\n    <span class=\"hljs-keyword\">this<\/span>.cl = cloudinary.Cloudinary.new({ <span class=\"hljs-attr\">cloud_name<\/span>: <span class=\"hljs-string\">'christekh'<\/span> });\n    <span class=\"hljs-keyword\">this<\/span>.vDom = <span class=\"hljs-literal\">null<\/span>;\n    <span class=\"hljs-keyword\">this<\/span>.vPlayer = <span class=\"hljs-literal\">null<\/span>;\n  }\n  componentDidMount() {\n    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.vDom) {\n      <span class=\"hljs-keyword\">this<\/span>.vPlayer = <span class=\"hljs-keyword\">this<\/span>.cl.videoPlayer(<span class=\"hljs-keyword\">this<\/span>.vDom);\n      <span class=\"hljs-keyword\">this<\/span>.vPlayer.source(<span class=\"hljs-keyword\">this<\/span>.fetchPublicId(<span class=\"hljs-keyword\">this<\/span>.props.post.image));\n    }\n  }\n  \n  isVideo = <span class=\"hljs-function\"><span class=\"hljs-params\">url<\/span> =&gt;<\/span> url.split(<span class=\"hljs-string\">'.'<\/span>)&#91;url.split(<span class=\"hljs-string\">'.'<\/span>).length - <span class=\"hljs-number\">1<\/span>] === <span class=\"hljs-string\">'mp4'<\/span>;\n    \n  render() {\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n      &lt;video\n        controls\n        loop\n        id={<span class=\"hljs-keyword\">this<\/span>.vId}\n        className=<span class=\"hljs-string\">\"cld-video-player\"<\/span>\n        ref={vDom =&gt; (<span class=\"hljs-keyword\">this<\/span>.vDom = vDom)}\n    <span class=\"hljs-comment\">\/\/...<\/span>\n  }\n}\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Post;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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>Notice the component has been updated from functional to class. We need to maintain internal state with instance properties to keep track of things.<\/p>\n<h2>Image and Video Transformation<\/h2>\n<p>When you define width and height dimensions for an image or a video, you are not certain if the user adheres to your instructions and upload that dimension. In fact, a social network app would hardly recommend a dimension. Instead, it would accept whatever dimension the user sends and try as much as possible to make it fit into a design.<\/p>\n<p>Assuming we define a strict width and height of <code>687px<\/code> for the image and video, you may end up getting poorly scaled contents. With Cloudinary transformations, we can intelligently adapt the content in a way that it retains quality and we have consistence dimension.<\/p>\n<p><strong>Transforming Images<\/strong>\nThe SDK lets you exposes a <code>url<\/code> method that takes in an the public ID of a Cloudinary image and returns the full URL. We\u2019ve got no public ID but we have the full URL. The Public ID is the string right before the file extension in the URL. I wrote a method to extract it:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">isVideo = <span class=\"hljs-function\"><span class=\"hljs-params\">url<\/span> =&gt;<\/span> url.split(<span class=\"hljs-string\">'.'<\/span>)&#91;url.split(<span class=\"hljs-string\">'.'<\/span>).length - <span class=\"hljs-number\">1<\/span>] === <span class=\"hljs-string\">'mp4'<\/span>;\n<span class=\"hljs-comment\">\/\/...<\/span>\nfetchPublicId = <span class=\"hljs-function\"><span class=\"hljs-params\">url<\/span> =&gt;<\/span>\n    url.split(<span class=\"hljs-string\">'\/'<\/span>)&#91;url.split(<span class=\"hljs-string\">'\/'<\/span>).length - <span class=\"hljs-number\">1<\/span>].split(<span class=\"hljs-string\">'.'<\/span>)&#91;<span class=\"hljs-number\">0<\/span>];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>Now you can call the <code>url<\/code> method:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-18\" 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\">img<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{caption}<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{this.cl.url(this.fetchPublicId(image),<\/span> {\n    <span class=\"hljs-attr\">width:<\/span> <span class=\"hljs-attr\">687<\/span>,\n    <span class=\"hljs-attr\">height:<\/span> <span class=\"hljs-attr\">687<\/span>,\n    <span class=\"hljs-attr\">crop:<\/span> '<span class=\"hljs-attr\">pad<\/span>',\n  })} \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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>It takes the publicID and an optional transformation\/configuration object. The images now have defined width and height and any image that doesn\u2019t fit is padded.<\/p>\n<p><strong>Progressive Loading and Optimization<\/strong>\nYou will be amazed at how it simple it is to optimize images and load them progressively:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">&lt;img alt={caption} src={<span class=\"hljs-keyword\">this<\/span>.cl.url(<span class=\"hljs-keyword\">this<\/span>.fetchPublicId(image), {\n    <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-number\">687<\/span>,\n    <span class=\"hljs-attr\">height<\/span>: <span class=\"hljs-number\">687<\/span>,\n    <span class=\"hljs-attr\">crop<\/span>: <span class=\"hljs-string\">'pad'<\/span>,\n    <span class=\"hljs-attr\">flags<\/span>: <span class=\"hljs-string\">'progressive:steep'<\/span>,\n    <span class=\"hljs-attr\">quality<\/span>: <span class=\"hljs-string\">'auto'<\/span>\n  })} \/&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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>flags<\/code> value, <code>progressive:steep<\/code> loads a poor quality as fast as possible and then <strong>progressively<\/strong> updates the image with a higher quality image until it reaches a reasonable stage. Setting <code>quality<\/code> to auto enables the image to continue <strong>optimizing<\/strong> as long as the optimization is lossless (quality doesn\u2019t drop).<\/p>\n<p><strong>Transforming Videos<\/strong>\nYou can transform videos by passing a second argument, which is in the form of a config object to the <code>videoPlayer<\/code> method:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">componentDidMount() {\n    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.vDom) {\n      <span class=\"hljs-keyword\">this<\/span>.vPlayer = <span class=\"hljs-keyword\">this<\/span>.cl.videoPlayer(<span class=\"hljs-keyword\">this<\/span>.vDom, {\n          <span class=\"hljs-attr\">transformation<\/span>: {\n            <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-number\">687<\/span>,\n            <span class=\"hljs-attr\">height<\/span>: <span class=\"hljs-number\">687<\/span>,\n            <span class=\"hljs-attr\">crop<\/span>: <span class=\"hljs-string\">'pad'<\/span>\n          }\n        });\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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<h2>Hover to Mute\/Unmute<\/h2>\n<p>Let\u2019s discuss one of my major concerns &#8211; hovering to mute and unmute. Now that we have videos setup, this is a perfect time to start tackling this feature. Here is the plan:<\/p>\n<ol>\n<li>Mute videos on load<\/li>\n<li>Unmute on mouse enter<\/li>\n<li>Mute on mouse leave<\/li>\n<\/ol>\n<p>Let\u2019s start with muting the videos by default. This will still be done in <code>componentDidMount<\/code>, as soon as we configure the video:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">componentDidMount() {\n  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.vDom) {\n    <span class=\"hljs-keyword\">this<\/span>.vPlayer = <span class=\"hljs-keyword\">this<\/span>.cl.videoPlayer(<span class=\"hljs-keyword\">this<\/span>.vDom, {\n      <span class=\"hljs-comment\">\/\/...<\/span>\n    });\n    <span class=\"hljs-keyword\">this<\/span>.vPlayer.source(<span class=\"hljs-keyword\">this<\/span>.fetchPublicId(<span class=\"hljs-keyword\">this<\/span>.props.post.image));\n    <span class=\"hljs-comment\">\/\/ Mute by default<\/span>\n    <span class=\"hljs-keyword\">this<\/span>.vPlayer.mute();\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><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>Then once a user points a mouse to the video or moves the mouse off the video, we want to mute:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-22\" 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\">div<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"Post-image-bg\"<\/span>\n  <span class=\"hljs-attr\">onMouseEnter<\/span>=<span class=\"hljs-string\">{this.unMutePlayer}<\/span>\n  <span class=\"hljs-attr\">onMouseLeave<\/span>=<span class=\"hljs-string\">{this.mutePlayer}<\/span>\n&gt;<\/span>\n  \/* video and image tags*\/\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><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>The <code>onMouseEnter<\/code> is used to attach the <code>unMutePlayer<\/code> event to the video container and vice versa. You can create these methods to trigger the mute and unmute:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Post<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Component<\/span> <\/span>{\n  <span class=\"hljs-comment\">\/\/....  <\/span>\n  mutePlayer = <span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> <span class=\"hljs-keyword\">this<\/span>.vDom &amp;&amp; <span class=\"hljs-keyword\">this<\/span>.vPlayer.mute();\n  unMutePlayer = <span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> <span class=\"hljs-keyword\">this<\/span>.vDom &amp;&amp; <span class=\"hljs-keyword\">this<\/span>.vPlayer.unmute();\n}  \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><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<h2>Hide Bottom Controls<\/h2>\n<p>For an Instagram use case, the bottom control as shown in the image below is not so useful and it is distracting:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_700,c_fill\/dpr_auto\/media_bar.png\" alt=\"Instagram\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1400\" height=\"163\"\/><\/p>\n<p>We can hide it by adding a <code>*vjs-controls-disabled*<\/code> CSS class to the player:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-24\" 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\">video<\/span>\n    <span class=\"hljs-attr\">...<\/span>\n    <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"cld-video-player vjs-controls-disabled\"<\/span>\n    \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><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<h2>Fluid Videos<\/h2>\n<p>Making the video player responsive is as simple as it can get. Call the <code>cld-fluid<\/code> method on the player instance and pass the method <code>true<\/code> to get it kicking:<\/p>\n<pre><code>this.vPlayer.fluid(true);\n<\/code><\/pre>\n<h2>Scroll Hooks<\/h2>\n<p>Everyone\u2019s favorite feature about social network is infinite scroll \u2014 ability to load more content when you get to the bottom of the content. To implement this feature, you need a scroll hook \u2014 something that happens when a scroll occurs. It\u2019s just an event that we can add to the App component:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">constructor<\/span>(props) {\n  ...\n  this.state = {\n    <span class=\"hljs-comment\">\/\/...<\/span>\n    <span class=\"hljs-attr\">clientCount<\/span>: <span class=\"hljs-number\">3<\/span>,\n    <span class=\"hljs-attr\">serverCount<\/span>: <span class=\"hljs-number\">0<\/span>,\n    <span class=\"hljs-attr\">hasMorePosts<\/span>: <span class=\"hljs-literal\">false<\/span>\n  };\n  <span class=\"hljs-keyword\">this<\/span>.fetching\n  <span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">'scroll'<\/span>, () =&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (\n      <span class=\"hljs-keyword\">this<\/span>.bottomVisible &amp;&amp;\n      <span class=\"hljs-keyword\">this<\/span>.fetching === <span class=\"hljs-literal\">false<\/span> &amp;&amp;\n      <span class=\"hljs-keyword\">this<\/span>.state.hasMorePosts\n    ) {\n      <span class=\"hljs-keyword\">this<\/span>.loadPosts(<span class=\"hljs-keyword\">this<\/span>.state.offset, <span class=\"hljs-keyword\">this<\/span>.state.limit);\n    }\n  });\n}\n\ncomponentDidMount() {\n  <span class=\"hljs-keyword\">this<\/span>.loadPosts(<span class=\"hljs-keyword\">this<\/span>.state.offset, <span class=\"hljs-keyword\">this<\/span>.state.limit);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><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>When you scroll, the event is fired, and it checks if the bottom of the page is visible before loading posts. It also checks if the fetching instance variable is false (it\u2019s set to true when a request is going on). Finally, it checks if there are more posts that the server needs to send.<\/p>\n<p>The <code>bottomVisible<\/code> is a getter that returns a boolean if we have scrolled to the bottom:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">get<\/span> bottomVisible() {\n  <span class=\"hljs-keyword\">const<\/span> scrollY = <span class=\"hljs-built_in\">window<\/span>.scrollY;\n  <span class=\"hljs-keyword\">const<\/span> visible = <span class=\"hljs-built_in\">document<\/span>.documentElement.clientHeight;\n  <span class=\"hljs-keyword\">const<\/span> pageHeight = <span class=\"hljs-built_in\">document<\/span>.documentElement.scrollHeight;\n  <span class=\"hljs-keyword\">const<\/span> bottomOfPage = visible + scrollY &gt;= pageHeight;\n  <span class=\"hljs-keyword\">return<\/span> bottomOfPage || pageHeight &lt; visible;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><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>I moved <code>loadPosts<\/code> into a method so we can reuse it both in <code>componentDidMount<\/code> and when we scroll to the bottom. Here is the updated version:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">loadPosts(offset, limit) {\n  <span class=\"hljs-keyword\">this<\/span>.fetching = <span class=\"hljs-literal\">true<\/span>;\n  axios\n    .get(<span class=\"hljs-keyword\">this<\/span>.constructFetchUrl(offset, limit))\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ data: { posts, count } }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">this<\/span>.setState(\n        {\n          <span class=\"hljs-attr\">posts<\/span>: &#91;...this.state.posts, ...posts],\n          <span class=\"hljs-attr\">serverCount<\/span>: count,\n          <span class=\"hljs-attr\">hasMorePosts<\/span>: <span class=\"hljs-keyword\">this<\/span>.state.clientCount &lt; count ? <span class=\"hljs-literal\">true<\/span> : <span class=\"hljs-literal\">false<\/span>,\n          <span class=\"hljs-attr\">offset<\/span>: <span class=\"hljs-keyword\">this<\/span>.state.offset + <span class=\"hljs-keyword\">this<\/span>.state.limit,\n          <span class=\"hljs-attr\">clientCount<\/span>: <span class=\"hljs-keyword\">this<\/span>.state.clientCount + <span class=\"hljs-number\">3<\/span>\n        },\n        () =&gt; {\n          <span class=\"hljs-keyword\">this<\/span>.fetching = <span class=\"hljs-literal\">false<\/span>;\n        }\n      );\n    })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">err<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">console<\/span>.log(err));\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><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<h2>Scroll to Play<\/h2>\n<p>Another limitation of the Instagram Web is that unlike Instagram mobile, videos don\u2019t play when they are scrolled into the viewport. Cloudinary solves this with a simple configuration:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">this<\/span>.vPlayer = <span class=\"hljs-keyword\">this<\/span>.cl.videoPlayer(<span class=\"hljs-keyword\">this<\/span>.vDom, {\n  <span class=\"hljs-comment\">\/\/ Play video when in viewport<\/span>\n  <span class=\"hljs-attr\">autoplayMode<\/span>: <span class=\"hljs-string\">'on-scroll'<\/span>,\n  <span class=\"hljs-attr\">transformation<\/span>: {\n    <span class=\"hljs-comment\">\/\/.....<\/span>\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><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<h2>One Last Word<\/h2>\n<p>I made this prototype clone in a day \u2014 in fact, while waiting for a delayed flight. However, such a quick turnaround would not have been the case if I had chosen the route of implementing the whole transformation and video features myself. <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> does that for you, so you can focus more on writing business-specific code and not utility logics. Let me know your thoughts in the comments section below.<\/p>\n<p><a href=\"https:\/\/github.com\/christiannwamba\/cl-instagram\">CODE<\/a><\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":21678,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[25,303,311],"class_list":["post-21677","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-asset-management","tag-video","tag-video-transcoding"],"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>Improving Instagram Web With Progressive Image Loading<\/title>\n<meta name=\"description\" content=\"Learn how Cloudinary can improve Instagram with progressive image loading and optimization\" \/>\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\/improving_instagram_web_new_features_for_a_better_user_experience\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Improving Instagram Web With Progressive Image Loading\" \/>\n<meta property=\"og:description\" content=\"Learn how Cloudinary can improve Instagram with progressive image loading and optimization\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-02-12T19:18:31+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-09-21T11:44:57+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"1540\" \/>\n\t<meta property=\"og:image:height\" content=\"847\" \/>\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\/improving_instagram_web_new_features_for_a_better_user_experience#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Improving Instagram Web With Progressive Image Loading\",\"datePublished\":\"2018-02-12T19:18:31+00:00\",\"dateModified\":\"2025-09-21T11:44:57+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\"},\"wordCount\":7,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA\",\"keywords\":[\"Asset Management\",\"Video\",\"Video Transcoding\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2018\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\",\"url\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\",\"name\":\"Improving Instagram Web With Progressive Image Loading\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA\",\"datePublished\":\"2018-02-12T19:18:31+00:00\",\"dateModified\":\"2025-09-21T11:44:57+00:00\",\"description\":\"Learn how Cloudinary can improve Instagram with progressive image loading and optimization\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA\",\"width\":1540,\"height\":847},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Improving Instagram Web With Progressive Image Loading\"}]},{\"@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":"Improving Instagram Web With Progressive Image Loading","description":"Learn how Cloudinary can improve Instagram with progressive image loading and optimization","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\/improving_instagram_web_new_features_for_a_better_user_experience","og_locale":"en_US","og_type":"article","og_title":"Improving Instagram Web With Progressive Image Loading","og_description":"Learn how Cloudinary can improve Instagram with progressive image loading and optimization","og_url":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","og_site_name":"Cloudinary Blog","article_published_time":"2018-02-12T19:18:31+00:00","article_modified_time":"2025-09-21T11:44:57+00:00","og_image":[{"width":1540,"height":847,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience"},"author":{"name":"","@id":""},"headline":"Improving Instagram Web With Progressive Image Loading","datePublished":"2018-02-12T19:18:31+00:00","dateModified":"2025-09-21T11:44:57+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience"},"wordCount":7,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","keywords":["Asset Management","Video","Video Transcoding"],"inLanguage":"en-US","copyrightYear":"2018","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","url":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","name":"Improving Instagram Web With Progressive Image Loading","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","datePublished":"2018-02-12T19:18:31+00:00","dateModified":"2025-09-21T11:44:57+00:00","description":"Learn how Cloudinary can improve Instagram with progressive image loading and optimization","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","width":1540,"height":847},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Improving Instagram Web With Progressive Image Loading"}]},{"@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":""}]}},"parsely":{"version":"1.1.0","canonical_url":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","smart_links":{"inbound":0,"outbound":0},"traffic_boost_suggestions_count":0,"meta":{"@context":"https:\/\/schema.org","@type":"NewsArticle","headline":"Improving Instagram Web With Progressive Image Loading","url":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA&w=150&h=150&crop=1","image":{"@type":"ImageObject","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA"},"articleSection":"Uncategorized","author":[],"creator":[],"publisher":{"@type":"Organization","name":"Cloudinary Blog","logo":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA"},"keywords":["asset management","video","video transcoding"],"dateCreated":"2018-02-12T19:18:31Z","datePublished":"2018-02-12T19:18:31Z","dateModified":"2025-09-21T11:44:57Z"},"rendered":"<meta name=\"parsely-title\" content=\"Improving Instagram Web With Progressive Image Loading\" \/>\n<meta name=\"parsely-link\" content=\"https:\/\/cloudinary.com\/blog\/improving_instagram_web_new_features_for_a_better_user_experience\" \/>\n<meta name=\"parsely-type\" content=\"post\" \/>\n<meta name=\"parsely-image-url\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA&w=150&amp;h=150&amp;crop=1\" \/>\n<meta name=\"parsely-pub-date\" content=\"2018-02-12T19:18:31Z\" \/>\n<meta name=\"parsely-section\" content=\"Uncategorized\" \/>\n<meta name=\"parsely-tags\" content=\"asset management,video,video transcoding\" \/>","tracker_url":"https:\/\/cdn.parsely.com\/keys\/cloudinary.com\/p.js"},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723556\/Web_Assets\/blog\/Improving_Instagram_Web\/Improving_Instagram_Web.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21677","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=21677"}],"version-history":[{"count":6,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21677\/revisions"}],"predecessor-version":[{"id":38571,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21677\/revisions\/38571"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/21678"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=21677"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=21677"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=21677"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}