DEV Community

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

Posted on

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

Latest comments (0)