Updated on Jun 6th, 20215 min readreactgraphics

Build An eCommerce Color Search Tool With NodeJS + React | Part 2

In Part 1 of this series we created the server side logic for a color search tool using Node.js. In Part 2, we will create a user interface, using React, that consumes this data and filters the original list of images. A user will be able to select a color from our predetermined palette and the UI will display only the images that contain x percent of that color. The value of x is a threshold we can adjust, but in this example I choose 25%.

Check out this project on Code Sandbox

Getting Started

The eventual output of our server side application from Part 1 was a JSON file, containing the image color analysis data, that we will need for this part of the tutorial. The original images are also required. Even if you missed Part 1, you can follow Part 2 by downloading both links below.

Download Image Analysis JSON File

Download Images Zip File

Head over to CodeSandbox and create a new project using the React template.

Preparing The Sandbox

There are a few things we need to do to prepare our new sandbox to run our color search tool code.

  • change the css file extension to scss
  • unzip the images and upload them to the public folder
  • create data.js and copy the image analysis into it
  • create palette.js and copy the color palette into it
  • be sure to use export default on these two files
  • add bootstrap@5.0.1 as a dependency
palette.js
export default [
  { name: 'black', color: { R: 0, G: 0, B: 0 } },
  { name: 'gray', color: { R: 128, G: 128, B: 128 } },
  { name: 'silver', color: { R: 192, G: 192, B: 192 } },
  { name: 'white', color: { R: 255, G: 255, B: 255 } },
  { name: 'floral_white', color: { R: 255, G: 250, B: 240 } },
  { name: 'beige', color: { R: 245, G: 245, B: 220 } },
  { name: 'tan', color: { R: 218, G: 200, B: 160 } },
  { name: 'purple', color: { R: 103, G: 53, B: 126 } },
  { name: 'navy', color: { R: 19, G: 43, B: 83 } },
  { name: 'blue', color: { R: 31, G: 94, B: 158 } },
  { name: 'peacock', color: { R: 0, G: 128, B: 128 } },
  { name: 'aqua', color: { R: 85, G: 194, B: 195 } },
  { name: 'light_blue', color: { R: 144, G: 193, B: 228 } },
  { name: 'light_cyan', color: { R: 224, G: 255, B: 255 } },
  { name: 'dark_green', color: { R: 32, G: 75, B: 33 } },
  { name: 'green', color: { R: 36, G: 138, B: 15 } },
  { name: 'olive', color: { R: 128, G: 128, B: 0 } },
  { name: 'pale_green', color: { R: 163, G: 176, B: 133 } },
  { name: 'gold', color: { R: 222, G: 170, B: 13 } },
  { name: 'yellow', color: { R: 255, G: 210, B: 70 } },
  { name: 'lavender', color: { R: 230, G: 230, B: 250 } },
  { name: 'brown', color: { R: 79, G: 41, B: 7 } },
  { name: 'burgundy', color: { R: 117, G: 15, B: 23 } },
  { name: 'terracotta', color: { R: 178, G: 77, B: 56 } },
  { name: 'coral', color: { R: 247, G: 117, B: 100 } },
  { name: 'peach', color: { R: 251, G: 189, B: 147 } },
  { name: 'orange', color: { R: 250, G: 118, B: 10 } },
  { name: 'taupe', color: { R: 176, G: 156, B: 130 } },
  { name: 'red', color: { R: 192, G: 7, B: 24 } },
  { name: 'pink', color: { R: 235, G: 111, B: 164 } },
];

Writing The Code

This simple user interface will consist of two major parts:

  1. 1A grid of small clickable squares that represent our color palette
  2. 2A grid of larger images that represent our products or filterable items
App.js
import { useState } from "react";
import DATA from "./data";
import PALETTE from "./palette";
import "bootstrap/dist/css/bootstrap.min.css";
import "./styles.scss";

function formatColorName(name) {
  return name
    .replace("_", " ")
    .split(" ")
    .map((el) => el[0].toUpperCase() + el.slice(1))
    .join(" ");
}

export default function App() {
  const [color, setColor] = useState("");

  function colorFilter(x) {
    return color ? x.colorAnalysis[color] > 25 : true;
  }

  function colorSort(a, b) {
    return color ? b.colorAnalysis[color] - a.colorAnalysis[color] : 1;
  }

  return (
    <div className="container my-5">
      <div className="row">
        <div className="col-xl-3">
          <div className="color-tool">
            {PALETTE.map(({ name, color: { R, G, B } }) => (
              <div
                key={formatColorName(name)}
                style={{ backgroundColor: `rgb(${R},${G},${B})` }}
                className="square"
                onClick={() => setColor(name)}
              />
            ))}
            <div className="square" onClick={()=>setColor('')}>X</div>
          </div>
          {color && <div>{formatColorName(color)}</div>}
        </div>
        <div className="col-xl-9">
          <div className="products">
            {DATA.filter(colorFilter)
              .sort(colorSort)
              .map((d) => (
                <div key={d.relativePath} className="product">
                  <img
                    src={d.relativePath}
                    alt={d.relativePath}
                  />
                  {color && <div className='value'>{d.colorAnalysis[color]}</div>}
                </div>
              ))}
          </div>
        </div>
      </div>
    </div>
  );
}
Color Search Tool
Color Search Tool

Key Takeaways

In my original implementation, colors names were formatted using snake case (underscores replace spaces). This worked well with my database model but needs to be converted to standard case to be displayed in a UI. The formatColorName function accomplishes this conversion by chaining JavaScript string and array methods.

The App component UI is controlled by once state property for color, which holds the name of one of the colors from the color palette or an empty string. By mapping the PALETTE we are able to pass this color name to the setColor function and assign this callback as the onClick event handler. Now clicking on a color square will set the name of the color to the state variable color.

Since our React user interface will re-render when the value of color changes all we have to do is make the display of images (or imaginary products) depend on this value. In my opinion, this is basic ability is what makes React such a developer friendly tool. In this example we will chain a JavaScript filter method that uses color in its filtering logic to accomplish this. In a real world application the value of color would most likely be used in an API request to fetch the desired data. I chose a reasonable threshold of 25, meaning an image must be composed of at least 25% of the selected color to appear in the filtered results.

I also used a sort function to make the images appear in descending order based on the percentage of the selected color.

Style

This project is meant to be a simple and minimalistic demonstration of the filtering functionality. However, I included a small scss file, primarily to style the grids.

body {
  overflow-y: scroll;
  font-family: monospace;
}

.color-tool {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-gap: 5px;

  .square {
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center center;
    border: 1px solid lightgray;
    cursor: pointer;
  }
}

.products {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-gap: 50px;

  .product {
    display: grid;
    place-items: center center;

    img {
      width: calc(225px * .75);
      height: auto;
    }

    .value {
      font-size: 18px;
      font-weight: bold;
      margin-top: 5px;
    }
  }
}

@media only screen and (max-width: 576px) {
  .color-tool {
    grid-template-columns: repeat(5, 1fr);
    margin-bottom: 25px;
  }

  .products {
    grid-template-columns: 1fr 1fr;
    grid-gap: 10px;

    .product {
      img {
        width: 40vw;
      }
    }
  }
}

Final Thoughts

I hope this two part series gives readers a good starting point for implementing a similar feature in one of their projects. I tried to make this example as simple as I could. Sometimes if a tutorial is too specific, it can be hard to see how it might apply to your tech stack of choice. Here are a couple ideas to take this example project to the next level.

  • connect the server logic to a database to persist data
  • add color level sliders to the React UI
  • filter by multiple colors
  • add the ability for the user to upload an image for analysis
Benjamin Brooke Avatar
Benjamin Brooke

Hi, I'm Ben. I work as a full stack developer for an eCommerce company. My goal is to share knowledge through my blog and courses. In my free time I enjoy cycling and rock climbing.