-
Notifications
You must be signed in to change notification settings - Fork 14
Creating Front End Components
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.
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.
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.
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.
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.
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.
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.
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.