{"id":49,"date":"2025-07-01T14:27:39","date_gmt":"2025-07-01T18:27:39","guid":{"rendered":"https:\/\/www.shawntgray.com\/blog\/?p=49"},"modified":"2025-07-01T14:27:26","modified_gmt":"2025-07-01T18:27:26","slug":"how-to-build-an-art-gallery-with-react-part-6-wrap-up","status":"publish","type":"post","link":"https:\/\/www.shawntgray.com\/blog\/how-to-build-an-art-gallery-with-react-part-6-wrap-up\/","title":{"rendered":"How to Build an Art Gallery with React \u2013 Part 6: Wrap Up"},"content":{"rendered":"\n<p>Congratulations! Your hard work is done. Now it is time to package it and share your work with the world. But, before we do that, let&#8217;s review what we&#8217;ve done.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Check Your Project Directory<\/h2>\n\n\n\n<p>Your list of folders and files should look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>react_art_gallery\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 node_modules\n|   \u2514\u2500\u2500 <em>All the installed package files live here<\/em>\n\u251c\u2500\u2500 package-lock.json\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 public\n|   \u251c\u2500\u2500 images\n|   |   \u2514\u2500\u2500 <em>Your artwork that's featured in your gallery<\/em>\n\u2502   \u251c\u2500\u2500 index.html\n\u2502   \u2514\u2500\u2500 robots.txt\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 App.css\n    \u251c\u2500\u2500 App.js\n    \u251c\u2500\u2500 ImageCard.js\n    \u251c\u2500\u2500 ImageGallery.js\n    \u251c\u2500\u2500 images.json\n    \u251c\u2500\u2500 index.css\n    \u2514\u2500\u2500 index.js<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Double-Check Your Work<\/h2>\n\n\n\n<p>Click the file listed below to review the completed code for the component, stylesheet, etc.<\/p>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>App.css<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This CSS file holds all styles for your <strong>App<\/strong>, <strong>ImageGallery<\/strong>, and <strong>ImageCard <\/strong>components.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>.App {\n  text-align: center;\n  padding: 20px;\n  min-height: 100vh;\n}\n\n.gallery {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n  gap: 8px;\n  justify-items: center;\n}\n\n.image-card {\n  text-align: center;\n  max-width: 200px;\n}\n\n.image-card img {\n  max-width: 100%;\n  border-radius: 10px;\n  box-shadow: 0 4px 6px rgba(0, 0, 0, .1);\n  cursor: pointer;\n}\n\n.categories {\n  margin-top: 10px;\n  margin-bottom: 20px;\n  display: flex;\n  flex-flow: row wrap;\n  gap: 1em;\n  justify-content: center;\n}\n\n.category-pill {\n  padding: 5px 12px;\n  background-color: #333;\n  color: #f1f1f1;\n  border: 1px solid #f1f1f1;\n  border-radius: 20px;\n  font-size: 14px;\n  font-weight: 500;\n  transition: all .3s, color .3s;\n  cursor: default;\n}\n\n.category-pill:hover,\n.category-pill.active {\n  background-color: #428645;\n}\n\n.popover {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background-color: rgba(0, 0, 0, .7);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  z-index: 1000;\n  opacity: 0;\n  visibility: hidden;\n  transition: opacity .3s ease, visibility 0s .3s;\n}\n\n.popover.show {\n  opacity: 1;\n  visibility: visible;\n  transition: opacity .3s ease, visibility 0s 0s;\n}\n\n.popover-content {\n  background-color: rgba(0, 0, 0, .7);\n  padding: 25px 50px;\n  border-radius: 8px;\n  position: relative;\n}\n\n.popover-content img {\n  max-width: 100%;\n  max-height: 80vh;\n  object-fit: contain;\n  box-shadow: -5px 5px 10px rgba(0, 0, 0, .7);\n}\n\n.close-button {\n  position: absolute;\n  font-size: 1.25em;\n  font-weight: bold;\n  top: 10px;\n  right: 10px;\n  background-color: #f96e46;\n  color: white;\n  border: none;\n  border-radius: 50%;\n  padding: .25em .5em;\n  cursor: pointer;\n}<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>App.js<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This JavaScript file is the stage for all your other components and includes all high-level functionality to properly pass along properties and capture\/process events.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>import { useEffect, useState } from 'react';\nimport '.\/App.css';\nimport ImageGallery from '.\/ImageGallery';\nimport data from '.\/images.json';\n\nconst shuffleArray = (array) => {\n  const shuffledArray = &#91;...array];\n  for (let i = shuffledArray.length - 1; i > 0; i--) {\n    const j = Math.floor(Math.random() * (i + 1));\n    &#91;shuffledArray&#91;i], shuffledArray&#91;j]] = &#91;shuffledArray&#91;j], shuffledArray&#91;i]];\n  }\n  return shuffledArray;\n}\n\nfunction App(){\n\n  const &#91;filters, setFilters] = useState(&#91;]);\n  const &#91;images, setImages] = useState(&#91;]);\n  const &#91;selectedImage, setSelectedImage] = useState(null);\n  \n  useEffect(() =>{\n    const shuffledData = shuffleArray(data);\n    setImages(shuffledData);\n  }, &#91;]);\n  \n  const toggleFilter = (category) => {\n    setFilters((prevFilters) => {\n      if (prevFilters.includes(category)) {\n        return prevFilters.filter((filter) => filter !== category);\n      } else {\n        return &#91;...prevFilters, category];\n      }\n    });\n  }\n\n  const uniqueCategories = &#91;...new Set(images.flatMap((image) => image.categories))].sort();\n\n  const filteredImages = filters.length === 0\n    ? images\n    : images.filter((image) => filters.every((filter) => image.categories.includes(filter)));\n\t\n  const &#91;selectedImage, setSelectedImage] = useState(null);\n\n  const openPopover = (image) => { setSelectedImage(image); }\n  const closePopover = () => { setSelectedImage(null); }\n  \n  return (\n    &lt;div className='App'>\n      &lt;h1>Art Gallery&lt;\/h1>\n      &lt;p>Select categories to filter&lt;\/p>\n      &lt;div className='categories'>\n        {uniqueCategories.map((category) => (\n          &lt;button\n            key={category}\n            className={`category-pill`}\n            onClick={() => toggleFilter(category)}\n          >{category}&lt;\/button>\n        ))}\n      &lt;\/div>\n      {\n         filteredImages.length === 0\n           ? (\n             &lt;p style={{ color: 'red' }}>&lt;em>No images found for the selected categories.&lt;\/em>&lt;\/p>\n           )\n           : (\n             &lt;ImageGallery\n               images={filteredImages}\n               toggleFilter={toggleFilter}\n             \/>\n           )\n      }\n      \n      {\n         filteredImages.length === 0\n           ? (\n             &lt;p style={{ color: 'red' }}>&lt;em>No images found for the selected categories.&lt;\/em>&lt;\/p>\n           )\n           : (\n             &lt;ImageGallery\n               images={filteredImages}\n               toggleFilter={toggleFilter}\n               onImageClick={openPopover}\n             \/>\n           )\n      }\n\n      {\n        selectedImage &amp;&amp; (\n          &lt;div\n            className={`popover ${selectedImage ? 'show' : ''}`}\n            onClick={closePopover}\n          >\n            &lt;div\n              className='popover-content'\n              onClick={\n                (e) => e.stopPropagination()\n              }\n            >\n              &lt;img\n                src={`${process.env.PUBLIC_URL}${selectedImage.src}`}\n                alt={selectedImage.title}\n              \/>\n              &lt;p>\n                {selectedImage.title}\n              &lt;\/p>\n              &lt;button\n                className='close-button'\n                onClick={closePopover}\n              >\n                &amp;times;\n              &lt;\/button>\n            &lt;\/div>\n          &lt;\/div>\n        )\n      }\n    &lt;\/div>\n  );\n}\n\nexport default App;<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>ImageCard.js<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This is the child component of <strong>ImageGallery.js.<\/strong> This component renders the individual images along with their corresponding categories and titles. It also triggers the event to display the image&#8217;s full-screen <strong><em>Popover<\/em><\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>const ImageCard = ({ image, toggleFilter, onImageClick }) => {\n  return (\n    &lt;div className=\"image-card\">\n      &lt;img\n        src={`${process.env.PUBLIC_URL}${image.src}`}\n        alt={image.title}\n        onClick=(() => onImageClick(image))\n      \/>\n      &lt;p>&lt;strong>{image.title}&lt;\/strong>&lt;\/p>\n      &lt;div className=\"categories\">\n        {\n          image.categories.length > 0\n            ? (\n              image.categories.map((category) => (\n                &lt;span\n                  key={category}\n                  className=\"category-pill\"\n                  onClick={\n                    (e) => {\n                      e.stopPropagination();\n                      toggleFilter(category)\n                    }\n                  }\n                >{category}&lt;\/span>\n              ))\n            )\n            : (&lt;span>Add a category&lt;\/span>);\n        }\n      &lt;\/div>\n    &lt;\/div>\n  );\n}\n\nexport default ImageCard;<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>ImageGallery.js<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This is the child of the <strong>App <\/strong>component and <strong>ImageCard&#8217;s <\/strong>parent. This mostly transitory component contains all <strong>ImageCard <\/strong>components that are rendered based on the data fed to it from the <strong>App <\/strong>component &amp; <strong>images.json<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>import ImageCard from '.\/ImageCard';\n\nconst ImageGallery = ({ images, toggleFilter, onImageClick }) => {\n  return (\n    &lt;div className=\"gallery\">\n      {images.map((image, i) => (\n        &lt;ImageCard\n          key={i}\n          image={image}\n          toggleFilter={toggleFilter}\n          onImageClick={toggleFilter}\n        \/>\n      ))}\n    &lt;\/div>\n  );\n}\n\nexport default ImageGallery;<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>images.json<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This is your data file. Provide your image&#8217;s location, title, and any categories it belongs to. Your images.json file can contain as few or as many images as you like, as long as your JSON follows this pattern (replace all values with those that match your images):<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>&#91;\n  {\n    \"src\": \"\/path\/to\/image.png\",\n    \"categories\": &#91;\n      \"tag 1\",\n      \"another descriptor\",\n      \"3rd tag\",\n      \"tag the fourth\"\n    ],\n    \"title\": \"Image Title\"\n  },\n  {\n    \/\/same as before\n  }\n]<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>index.css<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This CSS file contains all general styles that affect the app and the page that contains it. Use this file to define general tags like body, code, button, a, h1-h5, p, etc.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>body {\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background-color: #2f2f2f;\n  color: #ddd;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n    monospace;\n}<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<details class=\"wp-block-details has-border-color has-base-border-color has-secondary-background-color has-background is-layout-flow wp-block-details-is-layout-flow\" style=\"border-style:solid;margin-top:0;margin-bottom:0;padding-top:0;padding-right:var(--wp--preset--spacing--x-small);padding-bottom:0;padding-left:var(--wp--preset--spacing--x-small);font-style:normal;font-weight:600\"><summary>index.js<\/summary>\n<div class=\"wp-block-group has-base-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-1268f1cd wp-block-group-is-layout-constrained\" style=\"margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--x-small);padding-right:var(--wp--preset--spacing--x-small);padding-bottom:var(--wp--preset--spacing--x-small);padding-left:var(--wp--preset--spacing--x-small)\">\n<p style=\"padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;font-style:normal;font-weight:100\">This is React&#8217;s entry point to your app. Your app can&#8217;t be built or rendered properly without this file. Your <strong>App <\/strong>component must be contained within the <strong>&lt;React.StrictMode><\/strong>, which is inside the <strong><em>root <\/em><\/strong>of your document.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-style:normal;font-weight:400\"><code>import React from 'react';\nimport ReactDOM from 'react-dom\/client';\nimport '.\/index.css';\nimport App from '.\/App.js';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  &lt;React.StrictMode>\n    &lt;App \/>\n  &lt;\/React.StrictMode>\n);<\/code><\/pre>\n<\/div>\n<\/details>\n\n\n\n<h2 class=\"wp-block-heading\">Build For Launch<\/h2>\n\n\n\n<p>Open a command prompt terminal of your choice and navigate to your project directory. Type the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run build<\/code><\/pre>\n\n\n\n<p>This will execute React&#8217;s build program, where it creates a fully optimized, compiled version of your project. In doing so, it checks your code for any confusions and errors. If it finds no errors, you should see a message similar to this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"530\" height=\"436\" src=\"https:\/\/www.shawntgray.com\/blog\/wp-content\/uploads\/2025\/06\/build-screenshot.png\" alt=\"NPM Run Build Screenshot\" class=\"wp-image-73\" srcset=\"https:\/\/www.shawntgray.com\/blog\/wp-content\/uploads\/2025\/06\/build-screenshot.png 530w, https:\/\/www.shawntgray.com\/blog\/wp-content\/uploads\/2025\/06\/build-screenshot-300x247.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><\/figure>\n\n\n\n<p>Open your newly created <strong>build <\/strong>folder to see a file directory similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>build\n\u251c\u2500\u2500 images\n|   \u2514\u2500\u2500 <em>Your artwork that's featured in your gallery<\/em>\n\u2514\u2500\u2500 static\n    \u251c\u2500\u2500 css\n    |   \u251c\u2500\u2500 main.abcdef01.css\n    |   \u2514\u2500\u2500 main.abcdef01.css.map\n    \u251c\u2500\u2500 js\n    |   \u251c\u2500\u2500 123.4567890a.chunk.js\n    |   \u251c\u2500\u2500 123.4567890a.chunk.js.map\n    |   \u251c\u2500\u2500 main.01234567.js\n    |   \u251c\u2500\u2500 main.01234567.js.LICENSE.txt\n    |   \u2514\u2500\u2500 main.01234567.js.map\n    \u251c\u2500\u2500 asset-manifest.json\n    \u251c\u2500\u2500 index.html\n    \u2514\u2500\u2500 robots.txt<\/code><\/pre>\n\n\n\n<p>Take all contents of this build folder and upload them to wherever you want your app to live. Once you do, you should be able to play around with it.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"720\" style=\"aspect-ratio: 1280 \/ 720;\" width=\"1280\" controls src=\"https:\/\/www.shawntgray.com\/blog\/wp-content\/uploads\/2025\/06\/art-gallery-preview-Made-with-Clipchamp.mp4\"><\/video><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Add-Ons &amp; Challenges<\/h2>\n\n\n\n<p>If you want to continue with this project, here are some ideas you can add to further improve your app&#8217;s usability:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add keyboard functionality to cycle through images when the Popover is visible. Also add clickable Previous \/ Next buttons to turn the Popover into a more dynamic carousel.<\/li>\n\n\n\n<li>Add thumbnails to the Popover of a few images that share the selected image&#8217;s category tags, giving higher placement to those that share the most categories.<\/li>\n\n\n\n<li>Add a text-input search bar that shows images with titles that match or contain the  input&#8217;s value.<\/li>\n\n\n\n<li><strong><em>Expert Challenge!<\/em><\/strong> Figure out how this can be integrated into WordPress or some other CMS as a plugin.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">CONGRATS ON A JOB WELL DONE!<\/h2>\n\n\n\n<p>Whether you take on any of the additional challenges or not, you should be proud of yourself. React can be confusing and complicated in the beginning, but once you understand how to build components, utilize useState and useEffect methods, and pass around properties and events, you can build anything your mind conceives!<\/p>\n\n\n\n<p>In the next series, I&#8217;ll show how to expand on this Art Gallery with an interactive, timed slideshow.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Finalize your React Art Gallery App. Learn how to build the project to launch on your website. Take on some challenges to level up your React skills!<\/p>\n","protected":false},"author":1,"featured_media":34,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-49","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-art-gallery"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/posts\/49","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/comments?post=49"}],"version-history":[{"count":3,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/posts\/49\/revisions"}],"predecessor-version":[{"id":75,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/posts\/49\/revisions\/75"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/media\/34"}],"wp:attachment":[{"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/media?parent=49"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/categories?post=49"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shawntgray.com\/blog\/wp-json\/wp\/v2\/tags?post=49"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}