Cross Platform Mobile App with Expo, Firestore, and Redux (part 3)

Welcome back for part 3 of this build along! This is going to be a shorter post that’s a quick detour from what I was planning to write about.

Over the weekend our team had a call, and one of our teachers from the bootcamp who was present recommended that I change the class components I was using to stateless functional components using React hooks.

Since I wasn’t too familiar with hooks I decided to take a quick sidebar and refactor my code to use them, which is what this post will be about. So let’s get to it!

Using React Hooks

I’d seen them before but never dove into how to use them. Turns out that the hook I’d need for this use case (useState) is incredibly simple and easy to implement. Had I known how to use them in the first place I’d probably have saved myself a lot of time and hassle!

Essentially, this is what’s necessary to use the useState hook and add local state to a stateless functional component:

import React, { useState } from 'react';

const ExampleComponent = () => {
	const [count, setCount] = useState(0);
	return (
		<View>
			<Button title="Increment" onPress={() => setCount(count + 1)}></Button>
			<Button title="Decrement" onPress={() => setCount(count - 1)}></Button>
			<Button title="Reset to 0" onPress={() => setCount(0)}></Button>
		</View>
	);
}

We import useState from react, then create an array within the component that contains the count and setCount items. We set that equal to calling the useState hook, passing in what will be the default or starting value of the state.

Then, anytime we need to access the state within the component we simply use the count variable. If we want to change the value of the count variable, we call the setCount method and pass in what we want the count to be.

This saves us a lot of bloat on our app, as stateless functional components consume less space than class components (from a server storage standpoint), and also simplifies our coded by getting rid of the need for all the this.props.blahblahblah calls. It’s just cleaner and simpler this way.

There were three components that needed to be refactored in this way: SignUpForm, LogInForm, and AuthPage. Here’s how they look after the refactoring:

// src/components/SignUpForm.js
import React, { useState } from 'react';
import { Text, View } from 'react-native';
import { Button, Input } from 'react-native-elements';
import validator from 'validator';
import { auth } from '../config/firebase';
import * as firebase from 'firebase';
import 'firebase/auth';
import { connect } from 'react-redux';
import { getUser } from '../redux/actions/user';

const SignUpForm = ({ dispatch, navigation }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [emailError, setEmailError] = useState('');
  const [passwordError, setPasswordError] = useState('');

  const handleEmailChange = (value) => {
    emailError === '' ? setEmail(value) : setEmailError('')
  }

  const handlePasswordChange = (value) => {
    passwordError === '' ? setPassword(value) : setPasswordError('')
  }

  const handleSubmit = () => {
    if (!validator.isEmail(email)) {
      setEmailError('Email address is invalid!');
    } else if (password.length < 8) {
      setPasswordError('Password must be at least 8 characters!');
    } else {
      firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
        .then(() => {
          auth.createUserWithEmailAndPassword(email, password)
            .then(res => {
              dispatch(getUser({
                email: res.user.email,
                uid: res.user.uid
              }))
              navigation.navigate('Home')
            }).catch(e => {
              console.log(e);
            })
        })
    }
  }

  return (
    <View
      style={{
        padding: 20
      }}
    >
      <Text
        style={{
          textAlign: 'center',
          fontWeight: '600',
          fontSize: '18px',
          color: 'gray'
        }}
      >Create an Account</Text>        
      <Input
        label="Email"
        placeholder="email@address.com"
        onChangeText={handleEmailChange}
        errorMessage={emailError}
        leftIcon={{ type: "material-community-icons", name: "email" }}
      />
      <Input
        label="Password"
        placeholder="Password"
        onChangeText={handlePasswordChange}
        secureTextEntry={true}
        errorMessage={passwordError}
        leftIcon={{ type: "font-awesome-5", name: "key" }}
      />
      <Button
        style={{marginHorizontal: 10}}
        title="Submit"
        onPress={handleSubmit}
      ></Button>
    </View>
  );
}

export default connect()(SignUpForm);
// src/components/LogInForm.js
import React, { useState } from 'react';
import { Text, View } from 'react-native';
import { Button, Input } from 'react-native-elements';
import validator from 'validator';
import { auth } from '../config/firebase';
import * as firebase from 'firebase';
import 'firebase/auth';
import { connect } from 'react-redux';
import { getUser } from '../redux/actions/user';

const LogInForm = ({ dispatch, navigation }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [emailError, setEmailError] = useState('');
  const [passwordError, setPasswordError] = useState('');

  const handleEmailChange = (value) => {
    emailError === '' ? setEmail(value) : setEmailError('');
  }

  const handlePasswordChange = (value) => {
    passwordError === '' ? setPassword(value) : setPasswordError('');
  }

  const handleSubmit = () => {
    if (!validator.isEmail(email)) {
      setEmailError('Email address is invalid!');
    } else if (password.length < 8) {
      setPasswordError('Password must be at least 8 characters!');
    } else {
      firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
        .then(() => {
          auth.signInWithEmailAndPassword(email, password)
            .then(res => {
              dispatch(getUser({
                email: res.user.email,
                uid: res.user.uid
              }))
              navigation.navigate('Home')
            }).catch(e => {
              console.log(e);
            })
        })
    }
  }

  return (
    <View
      style={{
        padding: 20
      }}
    >
      <Text
        style={{
          textAlign: 'center',
          fontWeight: '600',
          fontSize: '18px',
          color: 'gray'
        }}
      >Login to Your Account</Text>
      <Input
        label="Email"
        placeholder="email@address.com"
        onChangeText={handleEmailChange}
        errorMessage={emailError}
        leftIcon={{ type: "material-community-icons", name: "email" }}
      />
      <Input
        label="Password"
        placeholder="Password"
        onChangeText={handlePasswordChange}
        secureTextEntry={true}
        errorMessage={passwordError}
        leftIcon={{ type: "font-awesome-5", name: "key" }}
      />
      <Button
        style={{marginHorizontal: 10}}
        title="Submit"
        onPress={handleSubmit}
      ></Button>
    </View>
  );
}

export default connect()(LogInForm);
// src/components/AuthPage.js
import React, { useState } from 'react';
import { View } from 'react-native';
import SignUpForm from './SignUpForm';
import LogInForm from './LogInForm';
import { ButtonGroup } from 'react-native-elements';

const AuthPage = ({navigation}) => {
  const [selectedIndex, setSelectedIndex] = useState(0);
  const buttons = ['Login', 'Signup']
  const updateIndex = (index) => setSelectedIndex(index);

  const AuthView = () => {
    if (selectedIndex === 0) {
      return <LogInForm navigation={navigation} />
    } else if (selectedIndex === 1) {
      return <SignUpForm navigation={navigation} />
    }
  };
  return (
    <View>
      <ButtonGroup
        onPress={updateIndex}
        selectedIndex={selectedIndex}
        buttons={buttons}
        containerStyle={{height: 40}}
      />
      <AuthView />
    </View>
  );
}

export default AuthPage;

And with those changes made our app still functions exactly the same, but this time we’re using the latest features of React to reduce the bloat in our code.

That’s all for this one! I’ll get to those changes I referenced last time in the next post. Till then!

-Brandon