Skip to content

Creating Front End Components

James Keary edited this page Aug 20, 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 { participantStoreContext } from "../stores/ParticipantStore"
import { observer } from "mobx-react-lite"

const Participants = () => {
  const participants = useContext(participantStoreContext)

  return (
    <div className="participants">
      {participants.map(participant => (
        <li key={participant}>{participant}</li>
      ))}
    </div>
  )
})

export default Participants

Here is a React simple component (i.e. a React component thats a function as opposed to a class) that I called Participants. It uses React's Context API. The Context API is helpful in passing data around thru many nested components without having to explicitly set the props value all over the place. in creating simple CRUD (create, read, update, and delete) functionality, which is helpful for interacting with our database. This is done with the participantStoreContext from the file ../stores/ParticipantStore.

It also brings with it the use of React hooks. Simply put hooks expose some additional functionality to our simple component and simplify what we want to do:

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 { createContext } from "react"
import participantApi from "../api/participantApi"

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

  participants = observable([])

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

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

// uncomment this line to have the store on the dom and testable
// var store = (window.store = new ParticipantStore())

export const ParticipantStoreContext = createContext(new 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.

Because I also know that the participant data could potentially be used by other components, I also import createContext from React. The Context API of React is useful in that it allows us to share state across multiple components without techniques like lifting state up or prop-drilling. It is using React Hooks to do so. Hooks provide an easy way to pass data around our application (here is a link I found helpful about why hooks were added to React).

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.

Clone this wiki locally