4 Setup Frontend API Services and Utilities

  1. Make functions to extract the user from the JWT
  2. Make functions to handle login and signup
  3. Connect the signup form
  4. Setup the login form
  5. Swap between Login & Signup
  6. Make the NavBar React to logged in state of the user

JWT Breakdown

  • The token can contain whatever custom data (called claims) we want to put in it.
  • The token is cryptographically signed by the server when it is created so that if the token is changed in any way, it is considered invalid.
  • The token is encoded, but not encrypted. It is encoded (converted) using a standard known as base64url encoding so that it can be serialized across the internet or even be included in a URL's querystring. It may seem that encoded data is "secret" - it's not as you'll soon see!
> const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ';
> const payload = jwt.split('.')[1]  // only interested in the payload (claims)
> atob(payload)
< "{"sub":"1234567890","name":"John Doe","admin":true}"

The atob() method decodes a base-64 encoded string and btoa() base-64 encodes data.

Because the data in a JWT can be easily read, it's important not to include sensitive/secret data such as Social Security Numbers, etc.

Okay, JWT-based auth is cool, let's see how we use them in a SPA...

Typical Token-Based Flow in a SPA

The following depicts the typical flow of JWT-based auth in a SPA:

Additional clarification on the above steps:

  • STEP 1: Applies to logging in and signing up.
  • STEP 2: The JWT is created only after the login credentials have been validated, or the visitor signing up has been saved to the database.
  • STEP 3: After the JWT has been received by the client, it needs to be persisted, usually in local storage, so that it can be sent in future requests as needed (STEP 4).
  • STEP 4: We will be including the JWT with any request that needs to be authenticated on the server.
  • STEP 5: We will write a tidy middleware function used to validate the token and add the user data to Express's req object - cool beans for sure!
  • STEP 6: Send a response positive or negative back to the client

Final Front End Code

send-request

Note: The sendRequest function always returns a promise and we are passing that promise to the caller of checkToken.

Each of these utilities work together to make our life easier and reference eachother

import { getToken } from './users-service'

export default async function sendRequest (url, method = 'GET', payload = null) {
  // Fetch takes an optional options object as the 2nd argument
  // used to include a data payload, set headers, etc.
  const options = { method }
  if (payload) {
    options.headers = { 'Content-Type': 'application/json' }
    options.body = JSON.stringify(payload)
  }
  const token = getToken()
  if (token) {
    // Ensure headers object exists
    options.headers = options.headers || {}
    // Add token to an Authorization header
    // Prefacing with 'Bearer' is recommended in the HTTP specification
    options.headers.Authorization = `Bearer ${token}`
  }
  const res = await fetch(url, options)
  // res.ok will be false if the status code set to 4xx in the controller action
  if (res.ok) return res.json()
  throw new Error('Bad Request')
}

users-service.js

import * as usersAPI from './users-api'

export async function signUp (userData) {
  // Delete the network request code to the
  // users-api.js module which will ultimately
  // return the JWT
  const token = await usersAPI.signUp(userData)
  // Persist the token to localStorage
  window.localStorage.setItem('token', token)
  return getUser()
}

export async function login (credentials) {
  const token = await usersAPI.login(credentials)
  // Persist the token to window.localStorage
  window.localStorage.setItem('token', token)
  return getUser()
}

export function getToken () {
  const token = window.localStorage.getItem('token')
  // getItem will return null if no key
  if (!token) return null
  const payload = JSON.parse(atob(token.split('.')[1]))
  // A JWT's expiration is expressed in seconds, not miliseconds
  if (payload.exp < Date.now() / 1000) {
    // Token has expired
    window.localStorage.removeItem('token')
    return null
  }
  return token
}

export function getUser () {
  const token = getToken()
  return token ? JSON.parse(atob(token.split('.')[1])).user : null
}

export function logOut () {
  window.localStorage.removeItem('token')
}

users-api.js

import sendRequest from './send-request'

const BASE_URL = '/api/users'

export function signUp(userData) {
  return sendRequest(BASE_URL, 'POST', userData)
}

export function login(credentials) {
  return sendRequest(`${BASE_URL}/login`, 'POST', credentials)
}

Sign Up

import { Component } from 'react'
import { signUp } from '../../utilities/users-service'

export default class SignUpForm extends Component {
  state = {
    name: '',
    email: '',
    password: '',
    confirm: '',
    error: ''
  }

  handleChange = (evt) => {
    this.setState({ ...this.state, [evt.target.name]: evt.target.value, error: '' })
  }

  handleSubmit = async (evt) => {
    evt.preventDefault()
    try {
      const formData = { ...this.state }
      delete formData.error
      delete formData.confirm
      const user = await signUp(formData)
      this.props.setUser(user)
    } catch (error) {
      this.setState({ error: 'Sign Up Failed' })
    }
  }

  render () {
    const disable = this.state.password !== this.state.confirm
    return (
      <div>
        <div className='form-container'>
          <form autoComplete='off' onSubmit={this.handleSubmit}>
            <label>Name</label>
            <input type='text' name='name' value={this.state.name} onChange={this.handleChange} required />
            <label>Email</label>
            <input type='email' name='email' value={this.state.email} onChange={this.handleChange} required />
            <label>Password</label>
            <input type='password' name='password' value={this.state.password} onChange={this.handleChange} required />
            <label>Confirm</label>
            <input type='password' name='confirm' value={this.state.confirm} onChange={this.handleChange} required />
            <button type='submit' disabled={disable}>SIGN UP</button>
          </form>
        </div>
        <h1 className='error-message'>&nbsp;{this.state.error}</h1>
      </div>
    )
  }
}

Login

import { useState } from 'react'
import * as userService from '../../utilities/users-service'

export default function LoginForm ({ setUser }) {
  const [credentials, setCredentials] = useState({
    email: '',
    password: ''
  })
  const [error, setError] = useState('')

  const handleChange = (evt) => {
    setCredentials({ ...credentials, [evt.target.name]: evt.target.value })
    setError('')
  }

  const handleSubmit = async (evt) => {
    evt.preventDefault()
    try {
      const user = await userService.login(credentials)
      setUser(user)
    } catch (error) {
      setError(error.message)
    }
  }

  return (
    <div>
      <div className='form-container'>
        <form autoComplete='off' onSubmit={handleSubmit}>
          <label>Email</label>
          <input type='email' name='email' value={credentials.email} onChange={handleChange} required />
          <label>Password</label>
          <input type='password' name='password' value={credentials.password} onChange={handleChange} required />
          <button type='submit'>LOG IN</button>
        </form>
      </div>
      <h1 className='error-message'>&nbsp;{error}</h1>
    </div>
  )
}

Diagram of API Based Reactivity in React

React Review

Single Page Applications

Students will be able to:


Writing React Functional Components

You can write a react component using any method of writing functions. Check out the the below snippet.

Basically any function that returns JSX (HTML Like Syntax) React will treat as a component.

// Function Declaration
function Component1(props) {
  return <h1> Hello World </h1>
}

// Function Expression
const Component2 = function (props) {
  return <h1>Hello World</h1>
}

// Arrow Function
const Component3 = props => {
  return <h1> Hello World </h1>
}

// Showing them in use
function App(props) {
  return (
    <div>
      <Component1 />
      <Component2 />
      <Component3 />
    </div>
  )
}

Rules of JSX

JSX is the HTML like syntax we can use in React Components. There are several rules to keep in mind.

1. ONLY ONE TOP-LEVEL ELEMENT

GOOD

The div is the only top-level element

<div>
  <h1>Hello World</h1>
  <p>lorem ipsum</p>
</div>
BAD

The h1 and p are both at the top-level, this will cause an error.

<h1>Hello World</h1>
<p>lorem ipsum</p>
Also Good

If you really don't want to wrap the content in a div you can use empty tags which is called a "Fragment"

<>
<h1>Hello World</h1>
<p>lorem ipsum</p>
</>

2. Attributes are camelCase

All HTML attributes you are use to become camel case when writing them in JSX.

  • onclick becomes onClick
  • onchange becomes onChange
  • onSubmit becomes onSubmit
  • class becomes className (why? because the class keyword is already used in javascript)

You get the idea.

3. Inline styles in JSX

In normal html a inline style would be written like this.

<div style="display: flex; background-color: blue;">dfsfsfsdfsdf</div>

But JSX is NOT HTML it is just an HTML like abstraction over Javascripts DOM API. So when writing inline styles your dealing with the style object of the DOM node, so instead of a string you pass an object that will be merged into that nodes style object. Since it's javascript, all the CSS style attributes are now camel case instead of hyphenated.

think

let element = document.querySelector('#myElement');
element.style.backgroundColor = 'blue';
<div style={{display: "flex", backgroundColor: "blue"}}>dfsfsfsdfsdf</div>

4. ARRAYS WORK

You can pass arrays of JSX if you want.

return [<h1>Hello World</h1>, <h1>Hello World</h1>, <h1>Hello World</h1>]

Is the same as me writing

return (
  <>
    <h1>Hello World</h1>
    <h1>Hello World</h1>
    <h1>Hello World</h1>
  </>
)

This is why we use map because it returns a new array, so we could take an array of objects and map it to an array of JSX elements

5. INJECTING JAVASCRIPT EXPRESSIONS

Your JSX is treated as html, and anything in curly brackets are treated as Javascript expressions in the functions scope. Any valid javascript expression can be used this way.

return <h1> I am {30 + 5} years old </h1>

Props

Props allows a component to receive data from its parent component.

Some rules

  • Props can only be sent from a parent to a child
  • If the parent needs data from the child it should send a function as a prop then the child can pass its data to the function as an argument.
  • Anything can be sent as a prop, include JSX
//The Child Component
const Child = props => {
  //change the value of someVariable using function sent via props
  props.setter(8)

  return <h1>{props.stuff}</h1>
}

// THe Parent Component
const Parent = props => {
  let someVariable

  //function to set someVariable
  const setSV = data => {
    someVariable = data
  }

  // send down two props, stuff and setter
  return <Child stuff="hello world" setter={setSV} />
}

Using Arrays in React

Often times we may want generate JSX for many elements of an array, the standard way of doing so is using the array.map method. Use the example below to see how.

const Component = () => {
  // an array of dogs
  const dogs = [
    { name: "Sparky", age: 5 },
    { name: "Spot", age: 5 },
    { name: "Ralph", age: 5 },
    { name: "Fido", age: 5 },
  ]
  // map over the dogs array and create an array of JSX for each dog
  const dogJSX = dogs.map(dog => {
    // we return JSX for each dog in the array which we store in the dog variable, essentially we are looping over dog of dogs
    return (
      <div>
        <h1>{dog.name}</h1>
        <h2>{dog.age}</h2>
      </div>
    )
  })

  // the component returns JSX that uses the dogJSX array
  return <div>{dogJSX}</div>
}

Iterating Over an Object in React

Using Objects.keys to generate an array of strings that are the keys of the objects properties. You can then map over the array to generate JSX for each property.

const Component = props => {
  const Arthur = {
    name: "Arthur Bernier",
    age: "35",
    email: "ceo@arthurbernierjr.com",
  }

  return Object.keys(Arthur).map((key, index) => {
    return (
      <h2>
        {key}: {Arthur[key]}
      </h2>
    )
  })
}

The useRef hook

Think of the useRef hook kind of like document.querySelector, it let's you assign a DOM node to a variable so you can access its properties. React declarative (express what you want, not how to make it) nature makes it hard to write normal imperative (how to make the thing step by step) DOM code. So if you need to get access to a DOM node like an input you can do the following:

import { useRef } from "react"

const Component = props => {
  // create a new ref, we'll assign it in our JSX
  const inputRef = useRef(null)

  const handleClick = () => {
    //log the inputs elements value
    console.log(inputRef.current.value)
  }

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Click Me</button>
    </div>
  )
}

Form Handling

There are two ways to handle forms in React.

  • Controlled Forms: The value of the inputs are bound to state, so value of state and the value of the inputs are always in sync.
  • Uncontrolled Forms: The forms are not bound by state, instead their values are pulled using a ref when needed.

Example of a Controlled Form

Parts:

  • object holding form values as state
  • handleChange function that updates the state when we type into the form
  • handleSubmit function to handle form submission and do what you want with the data
import { useState } from "react"

export default function Form(props) {
  //State to hold the form data
  const [form, setForm] = useState({
    name: "",
    age: 0,
  })

  // handleChange function
  const handleChange = event => {
    // dynamically update the state using the event object
    // this function always looks the same
    setForm({ ...form, [event.target.name]: event.target.value })
  }

  const handleSubmit = event => {
    // prevent page refresh
    event.preventDefault()
    // do what you want with the form data
    console.log(form)
  }

  // The JSX for the form binding the functions and state to our inputs
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={form.name}
        onChange={handleChange}
        name="name"
        placeholder="write name here"
      />
      <input
        type="number"
        value={form.age}
        onChange={handleChange}
        name="age"
        placeholder="write age here"
      />
      <input type="submit" value="Submit Form" />
    </form>
  )
}

Example of an Uncontrolled Form

  • a ref created for each input
  • handleSubmit for when form is submitted
import { useRef } from "react"

export default function Form(props) {
  // ref to get input values
  const nameInput = useRef(null)
  const ageInput = useRef(null)

  const handleSubmit = event => {
    // prevent page refresh
    event.preventDefault()
    // do what you want with the form data
    console.log({
      name: nameInput.current.value,
      age: ageInput.current.value,
    })
  }

  // The JSX for the form binding the functions and state to our inputs
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={nameInput} placeholder="write name here" />
      <input type="number" ref={ageInput} placeholder="write age here" />
      <input type="submit" value="Submit Form" />
    </form>
  )
}

State Management

State is the most important concept in React, your app is "reactive" because you have state for data your UI depends on. As Apps get more complex, deciding how to handle state and where it should be housed can get quite daunting.

Here are some questions to use as a guide.

This Piece of State is Used in how many components?

The React Component Tree

  • 0-1: It should be in the one component using it and nowhere else
  • 2-5: It should be located in a parent all the components share but as low in the component tree as possible
  • 5+: Time to consider Context

Lifting State

The concept of Lifting state occurs when siblings need to share state with each other. The lifting state pattern occurs of the following pattern.

  • The state is housed in the parent of the two siblings
  • The parent passes a function as props to the sender to alter the parents state
  • The parent passes the state itself as a prop to the receiver to receive the updated state
// Component receive function as prop to update parents state
const SenderChild = props => {
  return <button onClick={() => props.update("Goodbye")}>Click Me</button>
}

// Component receives parents state
const ReceiverChild = props => {
  return <h1>{props.value}</h1>
}

// The parent who passes props to both children
const Parent = props => {
  // The State
  const [state, setState] = useState("Hello")

  // Function to update state to send to child
  const updateState = data => setState(data)

  // we pass the function and the state as props to the children
  return (
    <div>
      <ReceiverChild value={state} />
      <SenderChild update={updateState} />
    </div>
  )
}

Prop Drilling

This is the inevitable tragedy that occurs when your components trees grow to several layers. Imagine a piece of state is in a component that is needed in a grandchild component... you'd have to do the following.

const Parent = props => <Child cheese="gouda" />

const Child = props => <GrandChild data={cheese} />

const GrandChild = props => <h1>{props.data}</h1>

This is prop drilling, the Parent passes cheese to child, who passes the same data as data to GrandChild. Imagine if it was a Great-Great-Grandchild... that's a lot of typing just so one component can receive a single piece of data.

There are several solutions to this.

  • React Context
  • React useReducer Hook
  • The TaskRunner Pattern
  • Redux
  • And many more... (MobX, State Machines, ...)

Let's cover context and reducer!

Context

What context allows us to do is to create an object that be passed directly to children of any level without having to pass them around as props. If props were like walking down several flights of stairs, Context is liking taking an elevator to where you need to go, faster and easier.

import { createContext, useContext } from "react"

//create the context object
const context = createContext(null)

const GrandChild = props => {
  // consume the data from the provider in parent
  const ctx = useContext(context)
  return <h1>{ctx}</h1>
}

// notice... no props pass through child in this scenario
const Child = props => <GrandChild />

// the context provider determines what data the parent provides its children
const Parent = props => (
  <context.Provider value={"cheese"}>
    <Child />
  </context.Provider>
)

So notice, because we used Context, the parent component was able to pass data directly to it's grandchild without having to pass any props. Context makes transporting data across your components much easier. The only downside is the direction of the data and where it is used will be a little less obvious to a random spectator.

The useReducer Hook

Before context many use Redux for state management. Not only did Redux allow you to store all your state in one place (the Redux Store) but also allowed you to house all your stateful logic in one place called the Reducer function.

The reducer function would normally be passed an "action" which is an object with two properties. This action was passed to the reducer calling a "dispatch" function.

  • type: A string that is passed to a switch to determine how to update the state
  • payload: Any data needed for the state update.

React eventually took the core Redux functionality and built it in to React as the useReducer hook. Below is a basic example of the useReducer hook.

import { createContext, useContext, useReducer } from "react"

//create the context object
const context = createContext(null)

const GrandChild = props => {
  // consume the data from the provider in parent
  const ctx = useContext(context)
  // the h1 displays the state pulled from context
  // the buttons call dispatch and pass the action to the reducer
  return (
    <>
      <h1>{ctx.state}</h1>
      <button onClick={() => ctx.dispatch({ type: "add", payload: null })}>
        Add
      </button>
      <button onClick={() => ctx.dispatch({ type: "subtact", payload: null })}>
        Subtract
      </button>
    </>
  )
}

// notice... no props pass through child in this scenario
const Child = props => <GrandChild />

// the context provider determines what data the parent provides its children
const Parent = props => {
  // the reducer with our stateful logic
  const reducer = (state, action) => {
    // get the type and payload from the action
    const { type, payload } = action

    switch (type) {
      // determine how to update the state based on action type
      case "add":
        return state + 1
      case "subtract":
        return state - 1
      default:
        // if it doesn't match any type, keep the state as is
        return state
    }
  }

  // the initial value of the state
  const initialState = 0

  // create the state and the dispatch function
  const [state, dispatch] = useReducer(reducer, initialState)

  // pass the state and dispatch via context in an object
  return (
    <context.Provider value={{ state, dispatch }}>
      <Child />
    </context.Provider>
  )
}