4.5
(2)

Are you tired to fill in over and over again your credentials when you log in? Well let us do it for you, just introduce your user and password only once, tick the check button and from now on you can relax and just push the login button, because your credentials will be always waiting for you.” This could be a great commercial for your “Remember me” javascript functionality that you are about to write.

Before we start crafting the login form, let’s first take a look at the specifications:

The file structure

I’ve created a folder for this component called LoginForm and inside we have in a separate file all the styled-component’s CSS rules and in LoginForm.tsx the React/Typescript login form component.

The code

LoginFormStyle.tsx

import styled from 'styled-components';

type InputProps = {
    type: string,
    required: boolean
}

const Form = styled.form`
    max-width: 440px;
    margin: 0 auto;
    font: bold 30px Candara, Arial;
    color: #403866;
`
const Title = styled.h2`
    text-transform: uppercase;
    text-align: center;
`
const FormRow = styled.div<{bottomSpace?: number}>`
    margin-bottom: ${props => props.bottomSpace || 20}px;
`
const Input = styled.input.attrs(props => ({
type: props.type || "text",
required: true,
}))<InputProps>`
    background: #e6e6e6;
    border: 0;
    font-size: 18px;
    font-weight: bold;
    color: #403866;
    display: block;
    width: 100%;
    height: 62px;
    padding: 0 20px;
    box-sizing: border-box;
`
const CheckBox = styled.input.attrs(props => ({
type: "checkbox"
}))<{type: string}>`
    opacity: 0;
    position: absolute;
    ~ label {
        font-size: 16px;
        color: #999;
        display: block;
        position: relative;
        cursor: pointer;
        user-select: none;
        &::before {
            content: '.';
            color: transparent;
            display: inline-flex;
            justify-content: center;
            align-items: center;
            width: 18px;
            height: 18px;
            border-radius: 3px;
            background: #fff;
            border: 2px solid #827ffe;
            margin-right: 16px;
        } 
    }
    &:checked ~label::before {
        content: '\\2713';
        color: #827ffe;
    }
`
const Button = styled.input`
    background: #827ffe;
    font-size: 18px;
    color: #fff;
    text-transform: uppercase;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 0 20px;
    width: 100%;
    height: 62px;
    background-color: #827ffe;
    border-radius: 3px;
    border: 0;
    transition: all .4s;
    box-sizing: border-box;
    cursor: pointer;
    &:hover {
        background: #403866;
    }
`

export {Form , Title, FormRow, Input, CheckBox, Button}

LoginForm.tsx


import { useEffect, useState } from 'react';
import {Form , Title, FormRow, Input, CheckBox, Button} from './LoginFormStyle';
import { EncryptStorage } from 'encrypt-storage';

interface Credentials {
    user: string,
    password: string
}

const LoginForm = () => { 
    const encryptStorage = new EncryptStorage("SECRET_KEY");
    const [rememberMe, setRememberMe] = useState(false);
    const [formData, setFormData] = useState<Credentials>({
        user: '',
        password: ''
    });

    const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
        setFormData({
            ...formData, 
            [ev.target.name]:ev.target.value
        })
    }
    const handleCheck = () => {
        if(rememberMe) {
            encryptStorage.removeItem('loginCredentials');
        }
        setRememberMe(rememberMe => !rememberMe);
    }
    const handleSubmit = (ev: React.FormEvent<HTMLFormElement>) => {
        if(rememberMe) {
            encryptStorage.setItem("loginCredentials", JSON.stringify(formData));
        }
    }

    useEffect(() => {
        const storedData = encryptStorage.getItem('loginCredentials');
        if(storedData) { 
            setRememberMe(true);
            setFormData(storedData);
        }    
    }, [])

    return (
        <>
            <Title>Login</Title>
            <Form onSubmit={handleSubmit} action="">
                <FormRow>
                    <Input placeholder="Username" onChange={handleChange} name="user" value={formData.user} />
                </FormRow>
                <FormRow>
                    <Input type="password" placeholder="Password" onChange={handleChange} name="password" value={formData.password} />
                </FormRow>
                <FormRow bottomSpace={40}>
                    <CheckBox id="rememberMe" type="checkbox" onChange={handleCheck} checked={rememberMe}/>
                    <label htmlFor="rememberMe">Remember me</label>
                </FormRow>
                <Button type="submit" value="Login" />
            </Form>
        </>
    )
}
export default LoginForm;

The usage

We just import and call this component:

The explanation

Let’s start first with the style. In the LoginFormStyle.tsx we import the styled-components and we start writing rich style seasoned with props.

The FormRow styled component will be a div that can have an optional prop called bottomSpace and it’s type will be number. If that prop is not added on the component, than the margin-bottom will revert to 20 pixels.

In the first FormRow the margin bottom will be 20px on the 2nd it will be 40.

Since we are using password and text type inputs, we can predefine our attributes on the Input styled component:

If type is not specified on the component then it should revert to text type, anyway in html5 if you don’t specify a type, it will default to text type. The type of the attributes of this component is InputProps and it is defined before this code:

In order to style the checkbox, I created a fake checkbox with the ::before pseudo element on the label. When the label is clicked this CSS kicks in:

This adds an Ascii character representing the tick symbol.

In the end I just exported all styles in a single line:

Let’s cut to the chase after not cutting to the chase

The user and password fields and “Remember me” checkbox input are what we call “Controlled components” because they get their value from a state but then again the changes on that state are through a callback, like the onChange events callbacks.

When we type into the user or password fields the onChange event triggers and calls the handleChange event that has the event as argument.

In the handleChange function we update the formData state hook with an object that has the old values and then we overwrite it either with user: value or with password: value. I used here a variable property based on the name attribute on the input element to avoid making 2 functions: 1 for the user onchange and 1 for the password onchange events. Since we added the required attribute on the inputs, it won’t let you submit it without specifying both user and password.

By the way the Credentials interface that represents the formData looks like this:

The remember me checkbox

The checked state of the checkbox is kept in the rememberMe state hook and initially it’s false since it’s unchecked at the begining. But we added also a checked attribute on that checkbox and if that value is true the checkbox will be initially checked. When does that happen? Well later on we create a localstorage where we store the data that we introduced and on load event of the component we get that data and then we also check the checkbox, indicating that the value was autocompleted in both user and password fields.

When we check/uncheck the checkbox we call the handleCheck() function. First we want to know if the checkbox is checked when clicking on it, then it means we are in the unchecking process, so we can remove any storage that we set previously. I used encryptStorage instead of localStorage since we need to set up storages with encryption, especially that it is such sensitive data. Then we change the value in the state to the opposite of the old value.

How about the handleSubmit function?

When we hit the submit button we check if the remember me checkbox is checked and if it is then we create a local storage with the object converted into a string that we got from the formData state hook. The data on this storage will be encrypted and will look like this:

What happens next time when we open the login page?

We fetch the local storage data if there is any and we give the formData hook the fetched storage data and also set the rememberMe state to true because we want our checkbox checked. Since we have a controlled component after updating the formData state hook, the 2 fields automatically rerender with the stored values.

P.S. : For the encryption I used the encrypt-storage plugin

Thank you for your attention !

How useful was this post?

Click on a star to rate it!

Average rating 4.5 / 5. Vote count: 2

No votes so far! Be the first to rate this post.