DEV Community

Cover image for Facebook Sign Up Form Tutorial | React Binden💪👑 x Tailwindcss ️
Kingkor Roy Tirtho
Kingkor Roy Tirtho

Posted on

5 1

Facebook Sign Up Form Tutorial | React Binden💪👑 x Tailwindcss ️

React is an awesome FrontEnd UI library created by Facebook. But forms in React always been a little hard. This is what the library react-binden solves. It's a fairly new form handling library for React. It's extremely easy to learn & use

Get a deep dive on React Binden or visit the Docs

Tailwindcss is my most favorite css framework❤️ & by far the most awesome library I've ever found

This article only shows how to build a Signup form using React, react-binden & tailwindcss. I'm assuming one will be familiar with above tools & technologies

What are we Building?

We're making a simple, regular & boring old Sign Up Form inspired by Facebook's Sign Up Form with React, react-binden & tailwindcss. But there's a twist. The form will still be a Sign Up Form but we'll be honest for the placeholders, labels & license agreement etc.. texts🙃😆

Creating the project

For bootstraping the project, we'll use vite. An extraordinary frontend build tool that is super fast & also supports various frontend frameworks

Initiating the project

$ npm init vite
Enter fullscreen mode Exit fullscreen mode

It'll ask a few questions, including project name & which frontend framework to use. Write the name of your choice & select the react option

Now open the project into VSCode/your favorite code editor. Then in the terminal, inside the project root run

$ npm install
Enter fullscreen mode Exit fullscreen mode

Then remove all the non required files e.g src/App.css, src/logo.svg. Remove the all the boilerplate code inside src/App.jsx

Now install the following dependencies:

$ npm install react-binden tailwindcss postcss autoprefixer nanoid clsx
Enter fullscreen mode Exit fullscreen mode

Now run the following command to initiate TailwindCSS inside your project

$ npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

This will create the following files tailwind.config.js, postcss.config.js

Now add the following to src/index.css

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Let's enable JIT (Just in Time) mode for the tailwindcss compiler. Add mode: "jit" inside the code tailwind.config.js's export config object. Then the file should look like below:

module.exports = {
  // added jit mode
  mode: "jit",
  // purge Array
  purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  darkMode: "class", // or 'media' for automatic dark mode detection
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

Now run following command to start the vite dev server

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

Custom Themed Input

Now that we've done initializing the project, it's time to create a awesome & beautiful Input component with our favorite tailwindcss

Create a file as src/ModInput.jsx then do the following

import { Input } from 'react-binden'
import clsx from "clsx"
import { nanoid } from "nanoid"

function ModInput(props) {
  const id = props.id ?? nanoid()

  return (
    <div>
      {props.model.error && <p>{props.model.error}</p>}
      <Input id={id} {...props} />
      <label htmlFor={id}>{props.label}</label>
    </div>
  )
}

export default ModInput
Enter fullscreen mode Exit fullscreen mode

May be you're thinking why did I put the label & the error hint in the wrong order. Well, there's a reason. But now, let's style these components. I'll be using clsx for handling multiple & conditional classes efficiently

import { Input } from 'react-binden'
import clsx from "clsx"
import { nanoid } from "nanoid"

function ModInput(props) {
  const inputStyle = clsx(
    props.className,
    "peer transition-all p-1 border-2 border-solid rounded outline-none",
    {
      // conditional classes
      ["border-red-400"]: props.model.touched && !!props.model.error,
      ["border-gray-500 focus:border-blue-400"]: !props.model.error
    },
  )

  const id = props.id ?? nanoid()

  // radio & checkboxes are different than text fields thus they need
  // a bit differently adjusted styles
  const rowTypes = ["checkbox", "radio"]
  const secondDivStyles = clsx(
    "inline-flex",
    // corrects the wrong order of label & error-hint
    !rowTypes.includes(props.type) ? "flex-col-reverse" : "flex-row items-center"
  )

  const labelStyles = clsx(
    "transition-all select-none peer-focus:text-blue-500 font-semibold",
    { 
            ["font-normal peer-focus:text-black ml-2"]: rowTypes.includes(props.type),
      ["peer-focus:text-red-500"]: props.model.touched && !!props.model.error 
    }
  )

  return (
    <div className={secondDivStyles}>
      {props.model.error && (
                <p className="text-red-500 text-sm ml-2 group-focus">
                   {props.model.error}
                </p>)
      }
      <Input id={id} className={inputStyle} {...props} />
      <label htmlFor={id} className={labelStyles}>{props.label}</label>
    </div>
  )
}

export default ModInput
Enter fullscreen mode Exit fullscreen mode

Now, let's answer why the order of error-hint & label are in reverse in the JSX. This is because of tailwind's peer class & peer-focus: prefix/variant. TailwindCSS provides an awesome way to handle css's styles based on sibling's state. peer prefix works as the CSS's + operator for selectors. But peer only works when the upper most element/sibling has the peer class. Downwards siblings can use upward siblings states but not vice-versa

Learn more about TailwindCSS Sibling Select Variant

Basic Form

Let's use the newly created ModInput. Now in src/App.jsx we've to create our basic form using react-binden's Form, useModel & regex. We'll style the form later. Now only focus on Logic

import { Form, regex, useModel } from "react-binden"
import ModInput from "./ModInput"

function App() {
  // models of each field
  const email = useModel("")
  const password = useModel("")
  const confirmPassword = useModel("")
  const username = useModel("")
  const birthday = useModel("")
  // since we're using radio-group a common name for all the
  // radio-button is required to make it function
  const gender = useModel("", { name: "gender", required: true })

  function handleSubmit(_e, _states, { setSubmitting, resetForm }) {
      // resetting the form
            setInterval(() => {
              resetForm();
              setSubmitting(false);
            }, 500);
  }

  return (
    <div>
      <h1>Honest Facebook Sign Up</h1>
      <p><b>Disclaimer!:</b> This is just a parody of Facebook. Nothing related to actual Facebook corp. Made just for fun & entertainment</p>
      <Form onSubmit={handleSubmit}>
        <ModInput
          model={username}
          label="Username"
          // only allows lowercase letters
          pattern={[/^[a-z]+$/, "only lower case name is allowed"]}
          required
        />
        <ModInput
          type="email"
          label="Email"
          model={email}
          pattern={[regex.email, "Should be a valid email"]}
          required
        />
        <ModInput
          type="password"
          label="Password"
          model={password}
          pattern={[regex.moderatePassword, "Write a stronger password"]}
          required
        />
        <ModInput
          type="password"
          model={confirmPassword}
          imprint-model={password}
          label="Confirm Password"
          required
        />
        <ModInput
          type="datetime"
          label="Birthday"
          model={birthday}
          pattern={[regex.date_dd_MM_yyyy, "should follow the `ddmmyy` format"]}
          required
        />
        <div>
          <p>Gender</p>
          <div>
            <ModInput
              type="radio"
              model={gender}
              value="male"
              label="Male"
            />
            <ModInput
              type="radio"
              model={gender}
              value="female"
              label="Female"
            />
            <ModInput
              type="radio"
              model={gender}
              value="other"
              label="Other"
            />
          </div>
        </div>
        <div>
          <button type="submit">Get Ruined</button>
        </div>
      </Form>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

If you feel overwhelmed with all of the code above you can learn about react-binden here

Now, that we've all the fields Facebook requires to Signup, let's style & structure them as following

// ... import stuffs
function App() {
  // ... other stuff (models, handlers etc..)
  return (
    <div className="flex flex-col items-center">
      <h1 className="m-2 text-3xl text-center font-bold">
        Honest Facebook Sign Up
      </h1>
      <p className="text-center">
        <b>Disclaimer!:</b> This is just a parody of Facebook. Nothing related
        actual Facebook corp. Made just for fun & entertainment
      </p>
      <Form
        className="inline-flex flex-col p-5 space-y-2 max-w-xl"
        onSubmit={handleSubmit}
      >
        <div>
          <h2 className="text-2xl text-gray-900 font-semibold">Sign Up</h2>
          <p className="text-xs text-gray-600">
            It's quick & easy
          </p>
        </div>
        <hr />
        <ModInput
          model={username}
          label="Username"
          pattern={[/^[a-z]+$/, "only lower case name is allowed"]}
          required
        />
        <ModInput
          type="email"
          label="Email"
          model={email}
          pattern={[regex.email, "Should be a valid email"]}
          required
        />
        <div className="flex space-x-5">
          <ModInput
            type="password"
            label="Password"
            model={password}
            pattern={[regex.moderatePassword, "Write a stronger password"]}
            required
          />
          <ModInput
            type="password"
            model={confirmPassword}
            imprint-model={password}
            label="Confirm Password"
            required
          />
        </div>
        <ModInput
          type="datetime"
          model={birthday}
          pattern={[regex.date_dd_MM_yyyy, "should follow the `ddmmyy` format"]}
          required
        />
        <div>
          <p className="font-bold">Gender</p>
          <div className="flex items-center justify-between w-1/2">
            <ModInput type="radio" model={gender} value="male" label="Male" />
            <ModInput
              type="radio"
              model={gender}
              value="female"
              label="Female"
            />
            <ModInput type="radio" model={gender} value="other" label="Other" />
          </div>
        </div>
        <p className="text-gray-600 text-xs pb-5">
          By clicking Sign Up, you agree to our Terms, Data Policy and Cookie Policy. You may receive SMS notifications from us and can opt out at any time.
        </p>
        <div className="flex justify-center">
          <button
            type="submit"
            className="bg-[#00a400] py-2 px-10 text-white font-bold rounded"
          >
            Get Ruined
          </button>
        </div>
      </Form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The Fun Part

I hope it'd now look visually appealing but that's boring. Nothing fun & interesting. Of course, I could add awesome animations, weirdest scroll effect or various CSS animations. But we're developers & we do hard work "occasionally"🤥. So let's use our "joke power" (which I obviously don't have but still trying) with texts. Let's just pretend we're actual Facebook developer & we, for some reason have to be slightly honest with what we build🙃

FUN GENERATING

import { Form, regex, useModel } from "react-binden";
import ModInput from "./ModInput";

function App() {
  const email = useModel("");
  const password = useModel("");
  const confirmPassword = useModel("");
  const username = useModel("");
  const birthday = useModel("");
  const gender = useModel("", { name: "gender", required: true });

  function handleSubmit(_e, { errors }, { setSubmitting, resetForm }) {
    setInterval(() => {
      resetForm();
      setSubmitting(false);
    }, 500);
  }

  return (
    <div className="flex flex-col items-center">
      <h1 className="m-2 text-3xl text-center font-bold">
        Honest Facebook Sign Up
      </h1>
      <p className="text-center">
        <b>Disclaimer!:</b> This is just a parody of Facebook. Nothing related
        actual Facebook corp. Made just for fun & entertainment
      </p>
      <Form
        className="inline-flex flex-col p-5 space-y-2 max-w-xl"
        onSubmit={handleSubmit}
      >
        <div>
          <h2 className="text-2xl text-gray-900 font-semibold">Sign Up</h2>
          <p className="text-xs text-gray-600">
            It's quick & easy (profit for us)
          </p>
        </div>
        <hr />
        <ModInput
          model={username}
          label="Username"
          placeholder="Credit Card Pin. Oops, Username"
          pattern={[/^[a-z]+$/, "only lower case name is allowed"]}
          required
        />
        <ModInput
          type="email"
          label="Email"
          model={email}
          pattern={[regex.email, "Should be a valid email"]}
          placeholder="Password. Oh sorry, Email"
          required
        />
        <div className="flex space-x-5">
          <ModInput
            type="password"
            label="Password"
            model={password}
            pattern={[regex.moderatePassword, "Write a stronger password"]}
            placeholder="Why not use, Hail Zuckerberg?"
            required
          />
          <ModInput
            type="password"
            model={confirmPassword}
            imprint-model={password}
            label="Confirm Password"
            placeholder="Isn't it, Hail Zuckerberg?"
            required
          />
        </div>
        <ModInput
          type="datetime"
          label="Birthday (Makes it easier for your friends to beg treats from you)"
          model={birthday}
          pattern={[regex.date_dd_MM_yyyy, "should follow the `ddmmyy` format"]}
          required
        />
        <div>
          <p className="font-bold">Gender</p>
          <div className="flex items-center justify-between w-1/2">
            <ModInput type="radio" model={gender} value="male" label="Male" />
            <ModInput
              type="radio"
              model={gender}
              value="female"
              label="Female"
            />
            <ModInput type="radio" model={gender} value="other" label="Other" />
          </div>
        </div>
        <p className="text-gray-600 text-xs pb-5">
          By clicking Get Ruined, you agree that you're our product, we can do
          whatever we want with & we own you (for free). You may receive SMS
          notifications from us and can opt out at any time (not actually).
        </p>
        <div className="flex justify-center">
          <button
            type="submit"
            className="bg-[#00a400] py-2 px-10 text-white font-bold rounded"
          >
            Get Ruined
          </button>
        </div>
      </Form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Glad that it's finished. For a moment, it was feeling like it'd never end. But don't go way. There's a catch in the project. I created the entire website without taking care of responsiveness. So you can now make it responsive by yourself. Do this as a home-work

Results

After writing 2 million lines (200 actually) of code we're finally done. Let's see what have we built so far & let's hope there's no bug

Source Code: https://github.com/KRTirtho/fb-parody-signup

Social

Follow me on twitter

Follow me on Reddit

Give react-binden a ⭐ on Github

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay