Full Stack: User Reactions - blockchainpsu/blockchain-essentials-spring2020 GitHub Wiki
In this section, we will examine the extremely popular JavaScript framework, React.js—states, components, and JSX. Afterwards, we'll continue with our previous section's project by adding React functionality.
Note: The official React.js tutorial, like its parent social media company, is hot garbage. It uses several examples that are now considered deprecated. Use our suggested list from here instead.
We will cover the following:
React works by managing states within carefully-defined components that render certain interfaces for the frontend. Let's examine how each of these play together to create something more.
Components define what React should do and return what React should display on the screen when the website loads, or under certain conditions. Most of the time, we'll see components defined as functions.
// Make sure to import React
import React from 'react'
const ExampleComponent = (props) => {
//...
}
The props
are properties (parameters) of the component that will be crucial during their creation.
Example components could include a website's navigation bar, its body, a table, a list of elements, et cetera. Very importantly, some of these components can fit within other components.
By React convention, it's smarter to compartmentalize your components rather than rewrite something over and over again. This saves you development time, makes your code easier to read, and allows you to perform edits to all component elements quickly.
For example, look at the following pseudocode:
const List = (props) => {
return (
element1(content=c1)
element2(content=c2)
element3(content=c3)
element4(content=c4)
//...
elementn(content=cn)
)
}
I have n elements within my List
component that will display when I render List
. Each element is a separate entity. If n is small, then maybe I can manage them all. But if n is, say, 1000, then it becomes costly to edit all of my elements, in terms of development time.
Now let's see an example where I take advantage of components.
const Element = (props) => {
// do something ...
}
const List = (props) => {
return (
Element(content=c1)
Element(content=c2)
Element(content=c3)
Element(content=c4)
//...
Element(content=cn) // nth Element
)
}
By creating a new component, I can now change all of my Elements at once by editing my Element component. This saves time during debugging and the development cycle, and makes it more obvious where my Elements are coming from.
If you know about functional programming methods, such as map
, this becomes even simpler:
const Element = (props) => {
// do something ...
}
const List = (props) => {
const contents = [ c1, c2, c3, ..., cn ] // assuming contents are in an array
return (
contents.map((content, i) => Element(content=content, key=i))
)
}
State refers to React objects that hold a certain value that can change throughout the use of an interface without requiring the reload of a page.
States used to be defined within classes, but have since evolved with the introduction of React Hooks, which hook React functionality into function-defined components.
Let's take a look at a quick example below:
// Notice that you must import the useState hook
import React, { useState } from 'react'
const StatefulComponent = () => {
const [ state, setState ] = useState('')
return (
<p>{state}</p>
)
}
When I call useState()
, you can see that I simultaneously define two variables: state
and setState
. The first, state
, is the actual state object itself; it holds a value that will change depending on what the developer needs. The second is a setter function for the state; when called, it will set the value of state
to whatever its parameters are.
useState('')
initializes state
as an empty string in the above case. Likewise, we can initialize state
with many data types:
//...
const [ int, setInt ] = useState(0)
const [ bool, setBool ] = useState(false)
const [ arr, setArr ] = useState([])
const [ state, setState ] = useState(null)
React defines several conventions in regards to states that help (or hurt, depending on your perspective) clean programming practices.
For one, states should never be mutated directly. Their value should only be changed by using the setter function once the user interacts with the UI somehow.
Secondly, when using hooks in React, always put their declaration at the top of a React function. In every example in the course, you'll see this rule practiced. Having a set priority ensures that hooks are always consistently called in the same order, so the state of the hooks is preserved.
When we return
React components, they must define a structure of what to create on the interface. Up until now, this page has used pseudocode to demonstrate what such an effect may look like. In reality, it's much closer to a very familiar markup language.
Enter JSX, a JavaScript syntax extension that allows a developer to define the structure of a web page while sowing snippets of JavaScript in between.
JSX is extremely similar to HTML, down to the same tags, but has some key differences. For one, you can put in JavaScript code in JSX simply by enclosing the JavaScript snippet within curly brackets, like so: {js code goes here}
.
Consider the following components:
const InnerComponent = (props) => <h3>Hi, {props.name}!</h3>
const OuterComponent = (props) => {
const names = props.name
return (
<div>
<h3>Hi, {names[0]}!</h3>
<h3>Hi, {names[1]}!</h3>
<h3>Hi, {names[2]}!</h3>
// ...
</div>
)
}
You can see how JSX works in our return statements here, and it should be easy to understand if you've worked with HTML before. Note that return statements must always be wrapped by a <div></div>
or <></>
in JSX, or else the code won't accept your return statement.
But we can also see that this code snippet is a cry for help. This author is miserable and doesn't know how to use functional programming rules correctly, and if for some reason, the client wants to use <h2>
instead of <h3>
, he's a goner. So let's use our InnerComponent
to help simplify the process.
To return components within components, JSX wraps the component declaration like so: <ComponentName one={one} two={two} ... />
, which is a self-closing tag that carries props
through with added arguments like props.one=one
, in this case.
Combined with components and the map
function, we can make the previous snippet a little nicer:
const InnerComponent = (props) => <h3>Hi, {props.name}!</h3>
const OuterComponent = (props) => {
const names = props.name
return (
<div>
{names.map((name, i) => <InnerComponent name={name} key={i} />}
</div>
)
}
Also note that when repetitive processes are used (map
, <li>
, <tr>
, etc), JSX requires a key
property to be included in the Component.
So that wraps up our analysis of JSX. Up next, we'll see a practical example of creating a simple application with React.
Let's apply what we've learned to our suggestion-app
tutorial in our application.
Only continue after running npm install
in the same directory.
Our suggestion-app
directory is split into different directories: public
and src
. public
stores the assets (branding, icons and such) and main HTML file used for React. You'll notice in public/index.html
, there's a <div id="root"></div>
element; this is where we'll push our React code.
Next, we'll move to src/index.js
. You'll notice some JSX code with different className
s and such. That'll help create our visual element so that you don't have to struggle with it.
At the top of the file, add the following lines of code:
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import 'bulma/css/bulma.css';
import ReactDOM from 'react-dom';
The first import obviously sets up React. The second line adds routing functionality for different pages; we'll set this up last. The third is a CSS library called Bulma; similar to Bootstrap, but a lot more simplified. The fourth is what we'll examine in the next section.
At the end of the line, you'll see this line of code:
ReactDOM.render(<App />, document.getElementById('root'))
ReactDOM renders our main component, App, in public/index.html
in the root
div we examined before. That's it, easy game.
We're going to dive into our subcomponents now, which we'll add into our App component later. We'll start with SuggestionForm. Go into src/components/suggestionform.js
and we'll get started.
Once again, we'll import our necessary dependencies.
import React, { useState } from "react";
You'll notice our useState
hook for creating states, which should suggestion something important.
First, we'll declare our states. We need one state to store the subject value, and another for our body. We'll declare them like so:
const SuggestionForm = () => {
const [ subject, setSubject ] = useState("")
const [ body, setBody ] = useState("")
// ..
}
Next, let's adjust the web form components to handle our states.
First, lets look at our web form. We have a couple containers that hold different parts of our form: a subject text field, a body text area, and our buttons. We need the subject and body fields to show the values of our states.
So for the text field, we will adjust it like so:
input className="input" type="text" placeholder="suggestion about..." value={subject} onChange={(e) => setSubject(e.target.value)}/>
The value of our text field will always display the value of our subject state, and when the value of the text field changes according to the user, we will call the function in onChange: {(e) => setSubject(e.target.value)}
. This creates a function that sets the subject state to the value of the event e.
For the text area, we do the same thing, but with the body state:
<textarea className="textarea" placeholder="my suggestion is ..." value={body} onChange={(e) => setBody(e.target.value)}></textarea>
After that's done, we move on to the button. Notice how the submit button's type is submit
, which means that the submit
command executes on click.
To add that functionality, go to the original <form>
component and change it to <form onSubmit={handleSubmit}>
. That'll execute the handleSubmit
function when the button is clicked ... which means we need to define the handleSubmit
function.
Go a little above to const handleSubmit = (e) => {...}
and change it to the following:
const handleSubmit = (e) => {
e.preventDefault()
console.log(subject)
console.log(body)
setSubject('')
setBody('')
}
So, a quick explanation:
-
e.preventDefault()
stops the web form from doing stupid default stuff. - We print the subject and body states to the console to verify that the submit functionality works.
- Finally, we'll clear the subject and body states by calling their related setters.
For our cancel button, we'll do something similar. We'll change it like so:
<button className="button is-link is-light" onClick={handleCancel}>cancel</button>
And then we change our handleCancel
function to clear our values like so:
const handleCancel = () => {
e.preventDefault()
setSubject('')
setBody('')
}
After that, we have one final thing to change. To verify that our code works, we'll add a notification with conditional rendering to only display after the form is submitted. To do so, we'll need yet another state:
const [ showNotif, setShowNotif ] = useState(false)
And between our hero section and web form, we'll add the following component definition:
<SuccessNotification show={showNotif} handleShow={() => setShowNotif(false)}/>
So you'll see that our SuccessNotification requires two props: our showNotif state, and a handler to input a function that'll help with our conditional rendering (all it does is set our state to false).
Next, we go to the SuccessNotification component and write it like so:
const SuccessNotification = (props) => {
if (props.show) {
return (
<div className='notification is-success'>
<button className="delete" onClick={props.handleShow}></button>
Suggestion sent successfully!
</div>
)
} else {
return (<div></div>)
}
}
Very simply, this component creates a notification div if showNotif is true
, and shows an empty div if it isn't (which is to say, it shows nothing). If you click on the "delete" button, the notification disappears.
Also, we want the notification to appear when we submit, so we modify our handleSubmit
function like so:
const handleSubmit = (e) => {
e.preventDefault()
console.log(subject)
console.log(body)
setSubject('')
setBody('')
setShowNotif(true)
}
And finally, our SuggestionForm is done.
Okay, we now move to src/components/list.js
and do ... absolutely nothing. We'll return when we work on the backend and database.
Let's import our components into our main src/index.js
App component.
At the top of our page, after the other imports, we'll import our other components.
import List from './components/list'
import SuggestionForm from './components/suggestionform'
Okay, with those out of the way, we modify our actual App. See the <Router>
tags? Those help create routing for our pages to be directed to different URLs ('/' and '/create'). However, we need to create links to those parts.
In our navbar, we will add Links to redirect to our components.
<div className='navbar-start'>
<Link to='/' className='navbar-item'>
Home
</Link>
<Link to='/create' className='navbar-item'>
Create a Suggestion
</Link>
</div>
Then, in-between our navbar and footer, add the following Routes that work with our Links:
<Route path='/' exact component={List} />
<Route path='/create' component={SuggestionForm} />
Finally—minor change—enter your name into the footer. It's your work now.
We're done.
Okay, we run our React app by using the command npm start
and you should be able to see the app work.