CAPTCHA Test

Hide a site route behind a CAPTCHA test using React Router.


Environment

Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.

Specification

This is the first coding challenge I have written that requires React Router. I have tried to write challenges that require only React (and occasionally data-fetching libraries) to keep them as broadly applicable as possible. I also hoped that using fewer external libraries would extend the lifetime of the articles considering they would be vulnerable to fewer new releases/potential deprecations of functions used in the solution code. That said, routing is undoubtedly a fundamental facet of building websites, and React Router is a go-to library for single-page React apps looking to organize their site with subpages. I may include React Router in more challenges going forward, but I will try to keep other external libraries at a minimum. Because this is the first challenge with routing, I will keep it short and straightforward.

Create a React web app with two pages, a home (“/”) and a protected page (“/protectedPage”). The home page should contain a button that, when pressed, brings the user to “/protectedPage”. The user should be met with a CAPTCHA test that asks them to select a random digit from images of digits 1 through 6. If the user successfully completes the CAPTCHA test, display some sample protected content (in the solution below, I simply render a page with a single div). Your site should have the same behavior described above if the user goes directly to https://URL/protectedPage instead of clicking through the homepage.

Starter Code

Here is some code to get you started:

import React, { useState } from "react";
import { BrowserRouter, Link, Routes, Route } from "react-router-dom";

function App() {
  // YOUR CODE HERE
}

const Home = () => {
  // YOUR CODE HERE
};

const ProtectedPage = () => {
  // YOUR CODE HERE
};

const Captcha = () => {
  // YOUR CODE HERE
};

export default App;

The skeleton above has four components, but feel free to divide your code however you see fit. Also, you may want to edit the function declarations so that some components accept props from others.

Solution

import React, { useState } from "react";
import { BrowserRouter, Link, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="protectedPage" element={<ProtectedPage />} />
      </Routes>
    </BrowserRouter>
  );
}

const Home = () => (
  <Link to="/protectedPage" style={{ margin: 30 }}>
    <button>Locked</button>
  </Link>
);

const ProtectedPage = () => {
  const [unlocked, setUnlocked] = useState(false);

  return unlocked ? (
    <div>Secret message</div>
  ) : (
    <Captcha setUnlocked={setUnlocked} />
  );
};

const Captcha = ({ setUnlocked }) => {
  return (
    <div style={{ display: "flex", justifyContent: "center" }}>
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          flexDirection: "column",
          marginTop: 100,
          position: "fixed",
          zIndex: 2,
        }}
      >
        <Images
          answer={Math.floor(Math.random() * 5 + 1)}
          setUnlocked={setUnlocked}
        />
      </div>
    </div>
  );
};

const Images = ({ answer, setUnlocked }) => {
  const ONE =
    "https://images.pexels.com/photos/2249528/pexels-photo-2249528.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
  const TWO =
    "https://images.pexels.com/photos/1061141/pexels-photo-1061141.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
  const THREE =
    "https://images.pexels.com/photos/2249530/pexels-photo-2249530.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
  const FOUR =
    "https://images.pexels.com/photos/1061139/pexels-photo-1061139.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
  const FIVE =
    "https://images.pexels.com/photos/1010973/pexels-photo-1010973.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";
  const SIX =
    "https://images.pexels.com/photos/4772874/pexels-photo-4772874.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2";

  const isAnswer = (id) => {
    id == answer ? setUnlocked(true) : alert("Intruder!");
  };

  return (
    <>
      <div style={{ alignSelf: "center", fontSize: 20 }}>
        {"Select " + answer}
      </div>
      <div style={{ display: "flex", justifyContent: "center", flex: "row" }}>
        <img
          src={ONE}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(1)}
        />
        <img
          src={TWO}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(2)}
        />
        <img
          src={THREE}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(3)}
        />
      </div>
      <div style={{ display: "flex", justifyContent: "center", flex: "row" }}>
        <img
          src={FOUR}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(4)}
        />
        <img
          src={FIVE}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(5)}
        />
        <img
          src={SIX}
          style={{ width: 200, height: 200 }}
          onClick={() => isAnswer(6)}
        />
      </div>
    </>
  );
};

export default App;

The CAPTCHA component is all but identical to the solution included in another challenge without the router element, so I will not go into depth about its design in this article. The only difference, in this case, is that the component accepts a function to set the unlocked state variable if the user selects the correct image.

For this simple first challenge with React Router, we wrap two paths exactly as explained in the React Router documentation’s tutorial for beginners and pass the relevant URL slug and component to be rendered as props. On our home page, we make use of React Router’s Link component to route to “/protectedPage” when the user clicks a button, and in the ProtectedPage component, we conditionally render the CAPTCHA test or the protected content depending on the value of the state variable.

I will note that that this example is not very realistic because the protected content is easily visible through inspection in a browser; any visitor can access it without completing the test.


useState
routing