Full Stack: RESTful GET and POST - blockchainpsu/blockchain-essentials-spring2020 GitHub Wiki
Time to connect our server to the client. We're going to create suggestions with our Suggestion Form, add them to the database, and display all suggestions with our List (previously left basically-untouched).
This is a basic diagram explaining how our information goes from layer to layer.
GET methods start out with a request from the client for data. That request goes to our Node server, where it redirects the query to the database. The database exposes the data through the URL and sends it back to the server, where the server then sends the JSON of data to the original client as a response.
POST methods start as HTTP requests with certain arguments (in this case, the subject and body of the suggestion we want to add to the database) and sends it to the server. The server interprets the POST method as a desire to add it to the database, does so, and then returns a response: 200 if successful, and 404 if not.
We'll start with a POST method to add a suggestion to the database. This isn't terribly difficult; we just need a couple changes to our handleSubmit
function.
A familiar section. We need to install axios to help make API calls and GET/POST methods in general, so let's go ahead and install that by running npm install axios
in the src
directory.
Then, in src/components/SuggestionForm.js
, add the import to the top of the file.
import axios from 'axios'
We modify handleSubmit
like so:
const SuggestionForm = () => {
const [ subject, setSubject ] = useState("")
const [ body, setBody ] = useState("")
const [ showNotif, setShowNotif ] = useState(false)
const handleSubmit = (e) => {
e.preventDefault()
console.log(subject)
console.log(body)
// create a new suggestion and add to database
const newSuggestion = {
subject: subject,
body: body
}
console.log(newSuggestion)
axios.post('http://127.0.0.1:3001/suggestions/add', newSuggestion)
.then(res => console.log(res.data));
setSubject('')
setBody('')
setShowNotif(true)
}
// ...
}
Consider the lines that we've just recently added. We first make a JavaScript Object that contains the information we want to add to our database. Then, we create a POST method with axios and send it to our route for POST methods defined in server.js
. If that succeeds, we'll print the response data to make sure it matches what we sent. Everything else we did, we keep the same.
And with that, we're done. We can successfully add suggestions to the database.
Let's move on to the second HTTP request: the GET method. As a reminder, we're going to ask the server to "get" us information for our application, instead of "posting" new data.
In this case, we're going to display all the suggestions in our database as a table—we're going to get all our suggestions. Let's get started.
Open up our previously-untouched list.js
and you'll be greeted with a lot of nothing, with the exception of a basic HTML table element with a header. We also have a subcomponent, Suggestion
, that will add a table row <tr></tr>
with table data <td></td>
from our props: subject and body.
We'll be using the useState
hook to create states that manage the suggestions that we have here. While we do need to import that, we'll also need to import a different React hook: useEffect
. We'll explain how that works once we get there. For now, modify the React import statement like so:
import React, { useState, useEffect } from 'react'
Like before, we're also going to import axios to assist us, except with GET methods instead of POST methods this time. Add the following line after the React import statement at the top:
import axios from 'axios'
When we GET our suggestions, we'll have an array of JavaScript objects with subject
and body
fields, like how we defined them in our database. To handle that, we'll need a React state that stores an array of suggestions.
Well, it's actually a JavaScript object of an array of JavaScript objects, but those are petty semantics. You'll see what I mean. Also, the reason it's that weird is because of how our GET response looks like.
To create the state, we'll modify the main List component like so:
const List() = () => {
const [ suggestions, setSuggestions ] = useState({ suggestions: [{subject: 'hi', body: 'hi'}] })
// ...
}
Notice that I initialized my state with the value suggestions: [{subject: 'hi', body: 'hi'}]
. I did this for two reasons: one, so it's easier for you, the reader, to understand how the suggestions state should look, and two, to check if my axios GET method works correctly.
And speaking of axios, it's time to work on the GET method.
Okay, let's do this. First, we're going to introduce that hook I mentioned earlier: useEffect
.
While I would link React's docs here for further clarification, I have mentioned again and again about how horribly, terribly stupid their docs are, and I honestly think I can do a better job.
useEffect
basically contains code that will run only after the React component it's inside of has rendered. In this case, my component is my List, and I'm returning an HTML table. After the components render properly, we will run whatever's in the useEffect
hook (a function, usually).
In this case, we want to only grab data once my components have finished rendering, so we don't have to grab data and have it be useless if our interface doesn't work. So our hook will look like this:
const List = () => {
// state code goes here
useEffect( () => {
axios.get('http://127.0.0.1:3001/suggestions/')
.then(res => setSuggestions(res.data))
.catch((err) => console.log(err))
}
}
As you can see, we just wrap the useEffect()
hook around a new function with code that we want executed: updating the list. We use axios.get(...)
to get our data from our database server, and then will set our suggestions state to the response data. If something goes wrong, we print it to the console instead.
I should probably mention how axios works.
JavaScript has an important concept called promises that are a type of asynchronous operation. Let's break down those vocab terms.
Let's start with asynchronicity. When we run lines of code, we expect each line to be executed line-by-line; we move on to the next line once the line before finishes executing. Think of this as a synchronous operation: moving with the normal control flow.
But what if we want something to load indefinitely, or want something to load while not stopping the execution of our code? For this extremely specific example, if I'm loading data from a database, I don't want my website to stop loading while I load data. For this, I'm going to use an asynchronous operation. And here's where promises come in.
Promises essentially fill in the value of a variable when code execution is still running. So in this case, when I'm trying to GET data, I will first have a Promise whose value will be one of the following:
- Pending - Wait for it to finish...
- Fulfilled - Success! Operation is complete and the real value is here.
- Rejected - Nice try, loser.
And usually, we will adapt our code to deal with each value of a Promise.
How does this work with axios? Well, axios relies on promises to perform its operations; while it's hard to notice sometimes with the POST method, you will definitely notice with GET. Look at our axios code:
axios.get('http://127.0.0.1:3001/suggestions/')
.then(res => handleSetSuggestions(res))
.catch((err) => console.log(err))
We have our initial code execution, the .get(...)
part, that will fetch code asynchronously. While the operation is being performed, it will have a Promise take place of its value as a proxy, which will be pending at first. If the Promise's value changes to fulfilled, then we will execute the .then(...)
statement. Similarly, if we are rejected instead, we will execute the .catch(...)
statement.
When we finish our application and the GET method, you'll sometimes notice that when the website loads, the table is empty and then populates with values less than a second later. That's the Promise at work: it lets everything else load while data is still being fetched by axios.
That was a little fast, but I hope it makes sense! If not, check out Mozilla's guide on Promises.
We need a way to create our list of suggestions as a table efficiently, given an array of them. To do this, we'll be using a function that maps our array of suggestions into table row components.
First, we'll add a <tbody>
under </thead>
in my return
statement of List like so:
<!--
<thead>
<tr>
<th>Subject</th>
<th>Suggestion</th>
</tr>
</thead>
-->
<tbody>
{generateList()}
</tbody>
All I'm doing is calling a JavaScript function, generateList()
, to help manage my table rows. Of course, I have to create this function now.
Under our useEffect
hook, add the following function:
const generateList = () => {
if (suggestions.length > 0) {
return suggestions.map( (suggestion, i) => <Suggestion content={suggestion} key={i} /> )
} else {
return null
}
}
If my suggestions actually exist, I'll add something, and if not, I'll add nothing. That's what the if statement's there for. However, the real juicy part is the condition's execution.
I return a new array, which is a map of my original suggestions state array. Basically, I go through every JavaScript object in suggestions and create a Suggestion component using it. The key
, i
, is there because of React convention. If the syntax confuses you, check out Mozilla's guide on Map.
Recall that my suggestions appear as something like this:
{
subject: "string",
body: "string"
}
And my Suggestion component will make a table row that will fill in with my JavaScript Object's subject and body values. So the mapping will create three table rows of suggestions if there are three suggestions in my database, or twelve thousand if there are twelve thousand suggestions in my database.
And with that, we're done. Finally.
I made my CSS for the HTML table using Bulma, as always, which has different class names for different variations of table looks. To do this, I referred to the documentation, which is always extremely informative.
This is going to sound really petty, but please delete "is-striped" from the <table className="...">
in the return statement for List. It's ugly and I hate it.
Your choice, though. I mainly put this here as an idea of how to style your JSX elements.
With the completion of our list component, we've finally finished our application. Make sure all your node_modules
are installed, and give it a run.
Recall that for everything to work, you have to execute
npm start
for the React interface,npm run watch
for the Node server, andmongod
(probablysugo mongod
) for the MongoDB database.
When everything's done, be proud of yourself for making a full-stack application. Your end result should look something like this.