create-react-app with TypeScript & Firebase Authentication [JavaScript]


Let’s implement a whole authentication mechanism in React with sign up, sign in, password reset, change password and sign out in TypeScript.

🐞 GitHub Repository

Whole files of this tutorial are as follows:

https://github.com/morizyun/react-typescript-firebase-auth

🎳 Get Firebase App info

It will be needs for using Firebase authentication.

🐰 create-react-app with TypeScript

You can install create-react-app on command line:

# If you have not install it yet, please do:
yarn global add create-react-app # npm install -g create-react-app

# Create new project
create-react-app react-ts-firebase-auth --scripts-version=react-scripts-ts
cd react-ts-firebase-auth

# Install Firebase and react-router-dom (React Router)
yarn add firebase react-router-dom

yarn add --dev @types/react-router @types/react-router-dom

After then, you can run app process by yarn start on terminal and open http://localhost:3000.

Also, please create some directories:

🍄 Configuration

Please change tslint.json to add as follows:

{
+ "rules": {
+ "jsx-no-lambda": false
+ }
}

🐮 Rebuild folders

# Create folders
mkdir src/components
mkdir src/constants
mkdir src/firebase
mkdir -p src/pages/Account
mkdir src/pages/Admin
mkdir src/pages/Home
mkdir src/pages/Landing
mkdir src/pages/PasswordForget
mkdir src/pages/SignIn
mkdir src/pages/SignUp

# Move files for App
mv src/components/App.ts src/components/App.ts

# Remove unused files
rm src/logo.svg
rm src/App.test.ts
rm src/App.css

🚜 Constants

Add src/constants/routes.ts file:

export const SIGN_UP = "/signup";
export const SIGN_IN = "/signin";
export const LANDING = "/";
export const HOME = "/home";
export const ACCOUNT = "/account";
export const PASSWORD_FORGET = "/password_forget";

🏀 Components

App

Create src/components/App.tsx file:

import * as React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import * as routes from "../constants/routes";
import { firebase } from "../firebase";
import { withAuthentication } from "../firebase/withAuthentication";
import { Account } from "../pages/Account";
import { Home } from "../pages/Home";
import { Landing } from "../pages/Landing";
import { PasswordForget } from "../pages/PasswordForget";
import { SignIn } from "../pages/SignIn";
import { SignUp } from "../pages/SignUp";
import { Navigation } from "./Navigation";

class AppComponent extends React.Component {
constructor(props: any) {
super(props);

this.state = {
authUser: null
};
}

public componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
authUser
? this.setState(() => ({ authUser }))
: this.setState(() => ({ authUser: null }));
});
}

public render() {
return (






true} path={routes.LANDING} component={Landing} />
true} path={routes.SIGN_UP} component={SignUp} />
true} path={routes.SIGN_IN} component={SignIn} />

exact={true}
path={routes.PASSWORD_FORGET}
component={PasswordForget}
/>
true} path={routes.HOME} component={Home} />

true} path={routes.ACCOUNT} component={Account} />
</Switch>
div>
</BrowserRouter>
);
}
}

export const App = withAuthentication(AppComponent);

Navigation

Create src/components/Navigation.tsx file:

import * as React from "react";
import { Link } from "react-router-dom";
import * as routes from "../constants/routes";
import { AuthUserContext } from "../firebase/AuthUserContext";
import { SignOutButton } from "./SignOutButton";

export const Navigation = () => (

{authUser => (authUser ? : )}
</AuthUserContext.Consumer>
);

const NavigationAuth = () => (


  • LandingLink>
    </li>

  • HomeLink>
    </li>

  • AccountLink>
    </li>

  • >
    </li>
    l>
    );

    const NavigationNonAuth = () => (


    • Landing</Link>
      li>

    • Sign In</Link>
      li>
      </ul>
      );

      PasswordChangeForm

      Create src/components/PasswordChangeForm.tsx file:

      import * as React from "react";
      import { auth } from "../firebase";

      interface InterfaceProps {
      error?: any;
      history?: any;
      passwordOne?: string;
      passwordTwo?: string;
      }

      interface InterfaceState {
      error?: any;
      passwordOne?: string;
      passwordTwo?: string;
      }

      export class PasswordChangeForm extends React.Component<
      InterfaceProps,
      InterfaceState
      > {
      private static INITIAL_STATE = {
      error: null,
      passwordOne: "",
      passwordTwo: ""
      };

      private static propKey(propertyName: string, value: string): object {
      return { [propertyName]: value };
      }

      constructor(props: any) {
      super(props);
      this.state = { ...PasswordChangeForm.INITIAL_STATE };
      }

      public onSubmit = (event: any) => {
      const { passwordOne }: any = this.state;

      auth
      .doPasswordUpdate(passwordOne)
      .then(() => {
      this.setState(() => ({ ...PasswordChangeForm.INITIAL_STATE }));
      })
      .catch(error => {
      this.setState(PasswordChangeForm.propKey("error", error));
      });

      event.preventDefault();
      };

      public render() {
      const { passwordOne, passwordTwo, error }: any = this.state;

      const isInvalid = passwordOne !== passwordTwo || passwordOne === "";

      return (
      event =>
      this.onSubmit(event)}>

      value={passwordOne}
      onChange={event => this.setStateWithEvent(event, "passwordOne")}
      type="password"
      placeholder="New Password"
      />

      value={passwordTwo}
      onChange={event => this.setStateWithEvent(event, "passwordTwo")}
      type="password"
      placeholder="Confirm New Password"
      />
      ="submit">

      Reset My Password
      </button>

      {error &&

      {error.message}p>}


      </form>
      );
      }

      private setStateWithEvent(event: any, columnType: string): void {
      this.setState(
      PasswordChangeForm.propKey(columnType, (event.target as any).value)
      );
      }
      }

      SignOutButton

      Create src/components/SignOutButton.tsx file:

      import * as React from "react";
      import { auth } from "../firebase";

      export const SignOutButton = () => (
      ="button" onClick={auth.doSignOut}>
      Sign Out
      </button>
      );

      🎉 firebase

      auth

      Create src/firebase/auth.tsx file:

      import { auth } from "./firebase";

      // Sign Up
      export const doCreateUserWithEmailAndPassword = (
      email: string,
      password: string
      ) => auth.createUserWithEmailAndPassword(email, password);

      // Sign In
      export const doSignInWithEmailAndPassword = (email: string, password: string) =>
      auth.signInWithEmailAndPassword(email, password);

      // Sign out
      export const doSignOut = () => auth.signOut();

      // Password Reset
      export const doPasswordReset = (email: string) =>
      auth.sendPasswordResetEmail(email);

      // Password Change
      export const doPasswordUpdate = async (password: string) => {
      if (auth.currentUser) {
      await auth.currentUser.updatePassword(password);
      }
      throw Error("No auth.currentUser!");
      };

      AuthUserContext

      Create src/firebase/AuthUserContext.ts file:

      import * as React from "react";

      export const AuthUserContext = React.createContext(null);

      db

      Create src/firebase/db.ts file:

      import { db } from "./firebase";

      // User API
      export const doCreateUser = (id: string, username: string, email: string) =>
      db.ref(`users/${id}`).set({
      email,
      username
      });

      export const onceGetUsers = () => db.ref("users").once("value");

      firebase

      Create src/firebase/firebase.ts file:

      import * as firebase from "firebase/app";
      import "firebase/auth";
      import "firebase/database";

      const config = {
      apiKey: "YOUR_API_KEY",
      authDomain: "YOUR_AUTH_DOMAIN",
      databaseURL: "YOUR_DATABASE_URL",
      messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
      projectId: "YOUR PROJECT_ID",
      storageBucket: "YOUR_STORAGE_BUCKET"
      };

      if (!firebase.apps.length) {
      firebase.initializeApp(config);
      }

      export const auth = firebase.auth();
      export const db = firebase.database();

      index

      Create src/firebase/index.ts file:

      import * as auth from "./auth";
      import * as db from "./db";
      import * as firebase from "./firebase";

      export { auth, db, firebase };

      withAuthentication

      Create src/firebase/withAuthentication.tsx file:

      import * as React from "react";
      import { firebase } from "../firebase";
      import { AuthUserContext } from "./AuthUserContext";

      interface InterfaceProps {
      authUser?: any;
      }

      interface InterfaceState {
      authUser?: any;
      }

      export const withAuthentication = (Component: any) => {
      class WithAuthentication extends React.Component<
      InterfaceProps,
      InterfaceState
      > {
      constructor(props: any) {
      super(props);

      this.state = {
      authUser: null
      };
      }

      public componentDidMount() {
      firebase.auth.onAuthStateChanged(authUser => {
      authUser
      ? this.setState(() => ({ authUser }))
      : this.setState(() => ({ authUser: null }));
      });
      }

      public render() {
      const { authUser } = this.state;

      return (


      </AuthUserContext.Provider>
      );
      }
      }
      return WithAuthentication;
      };

      withAuthorization

      Create src/firebase/withAuthorization.tsx fle:

      import * as React from "react";
      import { withRouter } from "react-router-dom";
      import * as routes from "../constants/routes";
      import { firebase } from "../firebase";
      import { AuthUserContext } from "./AuthUserContext";

      interface InterfaceProps {
      history?: any;
      }

      export const withAuthorization = (condition: any) => (Component: any) => {
      class WithAuthorization extends React.Component {
      public componentDidMount() {
      firebase.auth.onAuthStateChanged(authUser => {
      if (!condition(authUser)) {
      this.props.history.push(routes.SIGN_IN);
      }
      });
      }

      public render() {
      return (

      {authUser => (authUser ? : null)}
      </AuthUserContext.Consumer>
      );
      }
      }

      return withRouter(WithAuthorization as any);
      };

      🍮 Pages

      Account

      Create src/pages/Account/index.tsx file:

      import * as React from "react";
      import { PasswordChangeForm } from "./PasswordChangeForm";
      import { AuthUserContext } from "../../firebase/AuthUserContext";
      import { withAuthorization } from "../../firebase/withAuthorization";
      import { PasswordForgetForm } from "../PasswordForget/PasswordForgetForm";

      export const AccountComponent = () => (

      {authUser => (

      Account: {(authUser as any).email}</h1>


      >

      </div>
      )}
      AuthUserContext.Consumer>
      );

      const authCondition = (authUser: any) => !!authUser;

      export const Account = withAuthorization(authCondition)(AccountComponent);

      Create src/pages/Account/PasswordChangeForm.tsx file:

      import * as React from "react";
      import { auth } from "../../firebase";

      interface InterfaceProps {
      error?: any;
      history?: any;
      passwordOne?: string;
      passwordTwo?: string;
      }

      interface InterfaceState {
      error?: any;
      passwordOne?: string;
      passwordTwo?: string;
      }

      export class PasswordChangeForm extends React.Component<
      InterfaceProps,
      InterfaceState
      > {
      private static INITIAL_STATE = {
      error: null,
      passwordOne: "",
      passwordTwo: ""
      };

      private static propKey(propertyName: string, value: string): object {
      return { [propertyName]: value };
      }

      constructor(props: any) {
      super(props);
      this.state = { ...PasswordChangeForm.INITIAL_STATE };
      }

      public onSubmit = (event: any) => {
      const { passwordOne }: any = this.state;

      auth
      .doPasswordUpdate(passwordOne)
      .then(() => {
      this.setState(() => ({ ...PasswordChangeForm.INITIAL_STATE }));
      })
      .catch(error => {
      this.setState(PasswordChangeForm.propKey("error", error));
      });

      event.preventDefault();
      };

      public render() {
      const { passwordOne, passwordTwo, error }: any = this.state;

      const isInvalid = passwordOne !== passwordTwo || passwordOne === "";

      return (
      event =>
      this.onSubmit(event)}>

      value={passwordOne}
      onChange={event => this.setStateWithEvent(event, "passwordOne")}
      type="password"
      placeholder="New Password"
      />

      value={passwordTwo}
      onChange={event => this.setStateWithEvent(event, "passwordTwo")}
      type="password"
      placeholder="Confirm New Password"
      />
      ="submit">

      Reset My Password
      </button>

      {error &&

      {error.message}p>}


      </form>
      );
      }

      private setStateWithEvent(event: any, columnType: string): void {
      this.setState(
      PasswordChangeForm.propKey(columnType, (event.target as any).value)
      );
      }
      }

      Home

      Create src/pages/Home/index.tsx file:

      import * as React from "react";
      import { db } from "../../firebase";
      import { withAuthorization } from "../../firebase/withAuthorization";
      import { UserList } from "./UserList";

      class HomeComponent extends React.Component {
      constructor(props: any) {
      super(props);

      this.state = {
      users: null
      };
      }

      public componentDidMount() {
      db.onceGetUsers().then(snapshot =>
      this.setState(() => ({ users: snapshot.val() }))
      );
      }

      public render() {
      const { users }: any = this.state;

      return (

      Home Page</h2>


      The Home Page is accessible by every signed in user.p>



      {!!users && }
      </div>
      );
      }
      }

      const authCondition = (authUser: any) => !!authUser;

      export const Home = withAuthorization(authCondition)(HomeComponent);

      Create src/pages/Home/UserList.tsx file:

      import * as React from "react";

      interface InterfaceProps {
      users?: any;
      }

      export class UserList extends React.Component {
      constructor(props: any) {
      super(props);
      }

      public render() {
      const { users }: any = this.props;

      return (

      List of User name</h2>


      (Saved on Sign Up in Firebase Database)p>




        {Object.keys(users).map(key => {
        return
      • {users[key].username}</li>;

      • })}
        l>
        </div>
        );
        }
        }

        Landing

        Create src/pages/Landing/index.tsx file:

        import * as React from "react";

        export const Landing = () => {
        return (

        Landing Page</h2>


        div>
        );
        };

        PasswordForget

        Create src/pages/PasswordForget/index.tsx file:

        import * as React from "react";
        import { Link } from "react-router-dom";
        import { PasswordForgetForm } from "./PasswordForgetForm";

        export const PasswordForget = () => (

        PasswordForget</h1>


        >
        </div>
        );

        export const PasswordForgetLink = () => (


        ">Forgot Password


        );

        Create src/pages/PasswordForget/PasswordForgetForm.tsx file:

        import * as React from "react";
        import { auth } from "../../firebase";

        export class PasswordForgetForm extends React.Component {
        private static INITIAL_STATE = {
        email: "",
        error: null
        };

        private static propKey(propertyName: string, value: string) {
        return { [propertyName]: value };
        }

        constructor(props: any) {
        super(props);

        this.state = { ...PasswordForgetForm.INITIAL_STATE };
        }

        public onSubmit = (event: any) => {
        const { email }: any = this.state;

        auth
        .doPasswordReset(email)
        .then(() => {
        this.setState(() => ({ ...PasswordForgetForm.INITIAL_STATE }));
        })
        .catch(error => {
        this.setState(PasswordForgetForm.propKey("error", error));
        });

        event.preventDefault();
        };

        public render() {
        const { email, error }: any = this.state;
        const isInvalid = email === "";

        return (
        (event) =>
        this.onSubmit(event)}>

        value={email}
        onChange={(event) => this.setStateWithEvent(event, "email")}
        type="text"
        placeholder="Email Address"
        />
        ="submit">

        Reset My Password
        </button>

        {error &&

        {error.message}p>}


        </form>
        );
        }

        private setStateWithEvent(event: any, columnType: string): void {
        this.setState(
        PasswordForgetForm.propKey(columnType, (event.target as any).value)
        );
        }
        }

        SignIn

        Create src/pages/SignIn/index.tsx file:

        import * as React from "react";
        import { withRouter } from "react-router-dom";
        import { PasswordForgetLink } from "../PasswordForget";
        import { SignUpLink } from "../SignUp";
        import { SignInForm } from "./SignInForm";

        const SignInComponent = ({ history }: { [key: string]: any }) => (

        SignIn</h1>


        >


        </div>
        );

        export const SignIn = withRouter(SignInComponent);

        Create src/pages/SignIn/SignInForm.tsx file:

        import * as React from "react";
        import * as routes from "../../constants/routes";
        import { auth } from "../../firebase";

        interface InterfaceProps {
        email?: string;
        error?: any;
        history?: any;
        password?: string;
        }

        interface InterfaceState {
        email: string;
        error: any;
        password: string;
        }

        export class SignInForm extends React.Component<
        InterfaceProps,
        InterfaceState
        > {
        private static INITIAL_STATE = {
        email: "",
        error: null,
        password: ""
        };

        private static propKey(propertyName: string, value: any): object {
        return { [propertyName]: value };
        }

        constructor(props: InterfaceProps) {
        super(props);

        this.state = { ...SignInForm.INITIAL_STATE };
        }

        public onSubmit = (event: any) => {
        const { email, password } = this.state;

        const { history } = this.props;

        auth
        .doSignInWithEmailAndPassword(email, password)
        .then(() => {
        this.setState(() => ({ ...SignInForm.INITIAL_STATE }));
        history.push(routes.HOME);
        })
        .catch(error => {
        this.setState(SignInForm.propKey("error", error));
        });

        event.preventDefault();
        };

        public render() {
        const { email, password, error } = this.state;

        const isInvalid = password === "" || email === "";

        return (
        event =>
        this.onSubmit(event)}>

        value={email}
        onChange={event => this.setStateWithEvent(event, "email")}
        type="text"
        placeholder="Email Address"
        />

        value={password}
        onChange={event => this.setStateWithEvent(event, "password")}
        type="password"
        placeholder="Password"
        />
        ="submit">

        Sign In
        </button>

        {error &&

        {error.message}p>}


        </form>
        );
        }

        private setStateWithEvent(event: any, columnType: string): void {
        this.setState(SignInForm.propKey(columnType, (event.target as any).value));
        }
        }

        After then, please confirm http://localhost:3000 again. You can see some pages for sign up, sign in, password reset, change password and sign out.

        🗽 GitHub Repository

        Whole files of this tutorial are as follows:

        https://github.com/morizyun/react-typescript-firebase-auth

        😀 References

        🖥 Recommended VPS Service

        VULTR provides high performance cloud compute environment for you. Vultr has 15 data-centers strategically placed around the globe, you can use a VPS with 512 MB memory for just $ 2.5 / month ($ 0.004 / hour). In addition, Vultr is up to 4 times faster than the competition, so please check it => Check Benchmark Results!!