Skip to content

Creating Front End Components

James Keary edited this page Aug 21, 2019 · 20 revisions

This document is outlining the process of creating a React component and hooking it up to the backend. The front end is comprised of React components, some of which are used many times throughout the app, for example the navigation bar up top is a component that is generally on each url. Each component will have a corresponding store file for storing state information, api file for making api requests, and probably their own styles as well. For these instructions, I will be using the Participant Component.

Step one - Create the Component

import { observer } from "mobx-react"
import participantStore from "../stores/ParticipantStore"

export default
@observer
class Participant extends React.Component {
  constructor(props) {
    super(props)
    this.store = participantStore
    this.store.getParticipants()
  }

  render() {
    const store = participantStore
    return (
      <div>
        <p>Participants</p>
        <div className="participants">
          {store.participants.map((participant, index) => (
            <p key={index}>
              {participant.first_name} {participant.last_name}
              {participant.pp_id}
            </p>
          ))}
        </div>
        <div>
          <p>Participant Search by ID</p>
          <p>{this.store.filter("T9FN3", null, null).first_name}</p>
        </div>
      </div>
    )
  }
}

Here is a React class component that I called Participants. Things of note: we are using Mobx to deal with state changes and the store concept. State is any component data that is mutable/changable. Mobx provides us with a very easy library of functions that will help monitor those changes. We use the store concept as a place for all of our state-ly (mutable) data, from the file ../stores/ParticipantStore. As you can see in the component's constructor we are bringing in the store, and calling the getParticipants method in order to get a list of participants. We then put those participants into the html of the component.

Step Two - Create State

Does your compoent have state? A way to answer this question is to ask if your component has mutable/changable data on it, or does your component have a need to interact with the database? If not, you can skip to step 6, which is adding your component to the router. If so, you will need to add a place to store that data. For that, we use the concept of stores. Here is an example of what needs to be done with stateful components:

The ParticipantStore file looks something like this:

import participantApi from "../api/participantApi"

class ParticipantStore {
  constructor(rootStore) {
    this.rootStore = rootStore
  }

  //participants = observable([])
  @observable participants = []

  @action setParticipant(participant, index) {
    this.participants[index] = participant
  }

  getParticipants = flow(function*() {
    try {
      const results = yield participantApi.getParticipants()
      if (results) {
        results.data.forEach((datum, index) => {
          this.setParticipant(datum, index)
        })
      }
    } catch (error) {
      // TODO: Handle errors
      //console.log(error)
    }
  })

  filter(id, first, last) {
    //Filter on ID first, then name. Return a Participant or null
    const arr = toJS(this.participants)
    if (typeof id !== "undefined" && id !== null) {
      return arr.filter(x => x.pp_id === id)
    } else if (
      typeof first !== "undefined" &&
      first !== null &&
      typeof last !== "undefined" &&
      last !== null
    ) {
      return arr.filter(x => x.first_name === first && x.last_name === last)
    } else {
      return null
    }
  }
}

let participantStore = (window.participantStore = new ParticipantStore())
export default participantStore

Because I know that the Participants component will have mutable data in it, I started by importing some bits of Mobx that I will need to easily handle changes to state. And because I also know that I need to connect to the backend database, I imported an api file that I create in step 3.

Step Three - Create the API File

Heres the ParticipantAPI file:

import createAuthRefreshInterceptor from "axios-auth-refresh"
import refreshAuthLogic from "./refreshAuthLogic"

const create = () => {
  const accessToken = localStorage.getItem("JWT_ACCESS")

  const api = apisauce.create({
    baseURL: "/api",
    headers: { Authorization: `Bearer ${accessToken}` },
  })

  createAuthRefreshInterceptor(api.axiosInstance, refreshAuthLogic(api))

  const getParticipants = async () => {
    const response = await api.get("/participants/")
    return response
  }
  return {
    getParticipants,
  }
}

export default create()

All it is doing right now is creating a request to get all the participants from the database.

Step Four - Add the Store to the RootStore

The last real step to get things up and running is adding your new store to the RootStore so that it can be actually used on the DOM. RootStore looks like this:

import { AuthStore } from "./AuthStore"
import { ParticipantStore } from "./ParticipantStore"

export class RootStore {
  // If creating a new store dont forget to add it here.
  authStore = new AuthStore(this)
  participantStore = new ParticipantStore(this)
}

export const rootStoreContext = createContext(new RootStore())

now that we have added our participant store. The root store is already provided to the App, so all of our stores in the Root Store, are automatically usable on the DOM.

Step Five - testing

Testing is not really needed, although I think its a good idea to see if you are actually on the right track here. You may have noticed in the Participants Store the 2 lines that say this: // uncomment this line to have the store on the dom and testable // var store = (window.store = new ParticipantStore()) If you uncomment the var store line, you can now use store in your browser's dev console for testing. This is a great way to see if your requests are actually working yet. So for example, if you open up your App in your browser, open up up your dev tool console, and type store you should get back the ParticipantStore. And if you type store.getParticipant() you can actually hit that endpoint against your local backend and get all the participants from your local db (if its up and running). This way you can test your requests.

I also would suggest using Postman, its a helpful tool for creating the API requests without having to worry about all this other stuff first.

Step Six - Routing to the right URLs

The next step is getting the components on the right urls of the App. Now that we have the store and api up and running, lets display our component. The src/routes folder is where we will do this work. In the index.js file we can add our private route to the Router like so:

import Navbar from "../components/Navbar"
import LoginForm from "../components/LoginForm"
import ParticipantSearch from "../components/ParticipantSearch"
import Participants from "../components/Participant"
import { BrowserRouter as Router, Route } from "react-router-dom"
import PrivateRoute from "../routes/PrivateRoute"

const Routes = () => {
  return (
    <Router>
      <Navbar />
      <PrivateRoute exact path="/" component={ParticipantSearch} />
      <PrivateRoute exact path="/participants" component={Participants} />
      <Route path="/login" component={LoginForm} />
    </Router>
  )
}

export default Routes

We added the line: <PrivateRoute exact path="/participants" component={Participants} /> we marked it as a private route since it displays data that a user with credentials needs access to. If you look at the PrivateRoute.js file, you will see what I mean, it takes you back to the login screen if not authenticated. Now if you go to your browser and go to the /participants url you should see your component.

Step Seven - Styling and Displaying your Data

This section is currently a work in progress and I will be completing it soon... Here is where we can now start using Material-UI react components and or whatever styling components we want to build out how the data is displaying on the front end for the user.