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


Let’s implement a whole authentication mechanism in React & Redux 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-redux-typescript-firebase-auth

🎂 Get Firebase App info

It will be needs for using Firebase authentication.

🤔 create-react-app with Redux and 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 redux react-redux recompose

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

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/components/Account
mkdir src/components/App
mkdir src/components/Home
mkdir src/components/Landing
mkdir src/components/Navigation
mkdir src/components/PasswordChange
mkdir src/components/PasswordForget
mkdir src/components/Session
mkdir src/components/SignIn
mkdir src/components/SignOut
mkdir src/components/SignUp
mkdir src/constants
mkdir src/firebase
mkdir src/reducers
mkdir src/store

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

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

🍄 index

Create src/index.ts file:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { App } from "./components/App";
import "./index.css";
import registerServiceWorker from "./registerServiceWorker";
import { store } from "./store";

ReactDOM.render(


</Provider>,
document.getElementById("root")
);
registerServiceWorker();

😸 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

Account

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

import * as React from "react";
import { connect } from "react-redux";
import { compose } from "recompose";
import { withRouter } from 'react-router-dom'

import { PasswordChangeForm } from "../PasswordChange";
import { PasswordForgetForm } from "../PasswordForget/PasswordForgetForm";
import { withAuthorization } from "../Session/withAuthorization";

const AccountComponent = ({ authUser }: any) => (

Account: {authUser.email}</h1>


>

</div>
);

const mapStateToProps = (state: any) => ({
authUser: state.sessionState.authUser
});

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

export const Account = compose(
withAuthorization(authCondition),
withRouter,
connect(mapStateToProps)
)(AccountComponent);

App

Create src/components/App/index.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 { Account } from "../Account";
import { Home } from "../Home";
import { Landing } from "../Landing";
import { Navigation } from "../Navigation";
import { PasswordForget } from "../PasswordForget";
import { withAuthentication } from "../Session/withAuthentication";
import { SignIn } from "../SignIn";
import { SignUp } from "../SignUp";

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);

Home

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

import * as React from "react";
import { connect } from "react-redux";
import { compose } from "recompose";
import { withRouter } from 'react-router-dom'

import { db } from "../../firebase";
import { withAuthorization } from "../Session/withAuthorization";
import { UserList } from "./UserList";

class HomeComponent extends React.Component {
public componentDidMount() {
const { onSetUsers }: any = this.props;

db.onceGetUsers().then(snapshot => onSetUsers(snapshot.val()));
}

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

return (

Home</h1>


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



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

const mapStateToProps = (state: any) => ({
users: state.userState.users
});

const mapDispatchToProps = (dispatch: any) => ({
onSetUsers: (users: any) => dispatch({ type: "USERS_SET", users })
});

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

export const Home = compose(
withAuthorization(authCondition),
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
)
)(HomeComponent);

Create src/components/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 Usernames of Users</h2>


(Saved on Sign Up in Firebase Database)p>




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

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

    Landing

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

    import * as React from "react";

    export const Landing = () => {
    return (

    Landing Page</h2>


    div>
    );
    };

    Navigation

    Create src/components/Navigation/index.tsx file:

    import * as React from "react";
    import { connect } from "react-redux";
    import { Link } from "react-router-dom";

    import * as routes from "../../constants/routes";
    import { SignOutButton } from "../SignOut";

    const NavigationComponent = ({ authUser }: any) => (
    {authUser ? : }</div>

    );

    const NavigationAuth = () => (


    • LandingLink>
      </li>

    • HomeLink>
      </li>

    • AccountLink>
      </li>

    • >
      </li>
      l>
      );

      const NavigationNonAuth = () => (


      • Landing</Link>
        li>

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

        const mapStateToProps = (state: any) => ({
        authUser: state.sessionState.authUser
        });

        export const Home = compose(
        withRouter,
        connect(mapStateToProps)
        )(NavigationComponent);

        PasswordChange

        Create src/components/PasswordChange/index.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)
        );
        }
        }

        PasswordForget

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

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

        export const PasswordForget = () => (

        PasswordForget</h1>


        >
        </div>
        );

        export const PasswordForgetLink = () => (


        Forgot PasswordLink>
        </p>
        );

        Create src/components/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)
        );
        }
        }

        🐯 Session

        Create src/components/Session/withAuthentication.tsx file:

        import * as React from "react";
        import { connect } from "react-redux";
        import { firebase } from "../../firebase";

        interface InterfaceProps {
        authUser?: any;
        }

        interface InterfaceState {
        authUser?: any;
        }

        export const withAuthentication = (Component: any) => {
        class WithAuthentication extends React.Component<
        InterfaceProps,
        InterfaceState
        > {
        public componentDidMount() {
        const { onSetAuthUser }: any = this.props;

        firebase.auth.onAuthStateChanged(authUser => {
        authUser ? onSetAuthUser(authUser) : onSetAuthUser(null);
        });
        }

        public render() {
        return ;
        }
        }

        const mapDispatchToProps = (dispatch: any) => ({
        onSetAuthUser: (authUser: any) =>
        dispatch({ type: "AUTH_USER_SET", authUser })
        });

        return connect(
        null,
        mapDispatchToProps
        )(WithAuthentication);
        };

        Create src/components/Session/withAuthorization.tsx file:

        import * as React from "react";
        import { connect } from "react-redux";
        import { withRouter } from "react-router-dom";
        import { compose } from "recompose";
        import * as routes from "../../constants/routes";
        import { firebase } from "../../firebase";

        interface InterfaceProps {
        history?: any;
        authUser?: any;
        }

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

        public render() {
        return this.props.authUser ? : null;
        }
        }

        const mapStateToProps = (state: any) => ({
        authUser: state.sessionState.authUser
        });

        return compose(
        withRouter,
        connect(mapStateToProps)
        )(WithAuthorization);
        };

        SignIn

        Create src/components/SignIn/index.tsx:

        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/components/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));
        }
        }

        SignOut

        Create src/components/SignOut/index.tsx file:

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

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

        SignUp

        Create src/components/SignUp/index.tsx file:

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

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

        Create SingUpForm.tsx file:

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

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

        interface InterfaceState {
        email: string;
        error: any;
        passwordOne: string;
        passwordTwo: string;
        username: string;
        }

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

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

        constructor(props: InterfaceProps) {
        super(props);
        this.state = { ...SignUpForm.INITIAL_STATE };
        }

        public onSubmit(event: any) {
        event.preventDefault();

        const { email, passwordOne, username } = this.state;
        const { history } = this.props;

        auth
        .doCreateUserWithEmailAndPassword(email, passwordOne)
        .then((authUser: any) => {

        // Create a user in your own accessible Firebase Database too
        db.doCreateUser(authUser.user.uid, username, email)
        .then(() => {

        this.setState(() => ({ ...SignUpForm.INITIAL_STATE }));
        history.push(routes.HOME);
        })
        .catch(error => {
        this.setState(SignUpForm.propKey("error", error));
        });
        })
        .catch(error => {
        this.setState(SignUpForm.propKey("error", error));
        });
        }

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

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

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

        value={username}
        onChange={event => this.setStateWithEvent(event, "username")}
        type="text"
        placeholder="Full Name"
        />

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

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

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

        Sign Up
        </button>

        {error &&

        {error.message}p>}


        </form>
        );
        }

        private setStateWithEvent(event: any, columnType: string) {
        this.setState(SignUpForm.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.

        🍮 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!");
        };

        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 };

        🗻 reducers

        index

        Create src/reducers/index.ts file:

        import { combineReducers } from "redux";
        import { sessionReducer } from "./session";
        import { userReducer } from "./user";

        export const rootReducer = combineReducers({
        sessionState: sessionReducer,
        userState: userReducer
        });

        session

        Create src/reducers/session.ts file:

        const INITIAL_STATE = {
        authUser: null
        };

        const applySetAuthUser = (state: any, action: any) => ({
        ...state,
        authUser: action.authUser
        });

        export function sessionReducer(state = INITIAL_STATE, action: any) {
        switch (action.type) {
        case "AUTH_USER_SET": {
        return applySetAuthUser(state, action);
        }
        default:
        return state;
        }
        }

        user

        Create src/reducers/user.ts file:

        const INITIAL_STATE = {
        users: {}
        };

        const applySetUsers = (state: any, action: any) => ({
        ...state,
        users: action.users
        });

        export function userReducer(state = INITIAL_STATE, action: any) {
        switch (action.type) {
        case "USERS_SET": {
        return applySetUsers(state, action);
        }
        default:
        return state;
        }
        }

        🚜 store

        Create src/store/index.ts file:

        import { createStore } from "redux";
        import { rootReducer } from "../reducers";

        const store = createStore(rootReducer);

        export default store;

        🐹 GitHub Repository

        Whole files of this tutorial are as follows:

        https://github.com/morizyun/react-redux-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!!