RFR Section 7 – Actually Building Our App


React

Updated Mar 14th, 2022

Table of Contents

Course Summary

Chapter Details

Ch. 41 – Profile Screen

Set up a new component named “Profile.js” and import it into the “Main.js” file. Create a “Route” tag with “path” prop of “/profile/:username” and render the “Profile” component in the JSX.

In the “HeadedLoggedIn.js” component update “Link” and “To” on anchor tag that contains the avatar image. Use “appState.user.username” inside of back ticks to make the link dynamic.

`/profile/${appState.user.username}`

Copy and paste in from “html-template/profile-posts.html” file in the course GitHub repo and update “class” to “className” and import “Page” component.

Import and use “axios” to send request to server to retrieve data for the user. In “Profile.js” import “useParams” from “react-router-dom.” Write “axios” code in “useEffect.” Get around not being able to use “async” keyword in “useEffect” with workaround where you define and immediately call function. Use template literal in URL for username.

import {useParams} from 'react-router-dom'
import Axios from "axios"

function Profile() {
  const {username} = useParams()

  useEffect(() => {
  async function fetchData () {
  try {
    const response = await Axios.post(`/profile/${username}`, {token: appState.user.token})
    console.log(response.data)
  } catch(e) {
      console.log("There was an error: " + e)
  }
  }

  fetchData()

  return ()=> {}

  }, [])


}

export default Profile

Note: the reason we have “{username}” wrapped in curly brackets is because “useParams()” will return an object that will have many many properties.

Import “StateContext” so you can send token from “appState.user.token.”

Logging our “response.data” will get back an object with all kinds of data. We want to display data but we won’t have it right away so need to render placeholder values.

Create a piece of state with “useState” for “profileData” and “setProfileData” with default value set to an object.

const [profileData, setProfileData] = useState({
  profileUsername: "...",
  profileAvatar: "https://gravatar.com/avatar/[placeholder?s=128", // generic gravatar photo
  isFollowing: false,
  counts: {postCount: '', followerCount: "", followingCount: ""}
})

Update the JSX with the properties from this state object. Update the “axios” request to update the state with the content is received with “setProfileData(response.data)” line.

This will then be shown in the user interface.

Ch. 42 – Load Posts by Author

In “Profile.js” file we want to move list group div into it’s own component called “ProfilePosts.js” by cut and pasting this code from “Profile.js” file in to the new component.

Now import and render the new “ProfilePosts” component into the “Profile.js” file. Leverage in JSX with self-closing “<ProfilePosts />” tag.

In “ProfilePosts.js” file set up two pieces of state with “useState.” The first is whether the request is finished loading or not called “isLoading,” with initial value of true.

Set up an if statement so if “(isLoading),” mean is “isLoading” is set to true, then “return” a div with “Loading…” as a placeholder value, (later we will switch this out for a spinning loading icon).

Note: the if statement block contains the “return” keyword and so if the condition is true, that JSX will render, but the lower returned JSX code will not render.

The second piece of state is the actual “[posts, setPosts]” with initial value of empty array.

Create a “useEffect” that runs only when the component first renders, (empty dependency array).

Inside the “useEffect” function create an “async” function called “fetchPosts” to make your “axios” request. The “fetchPosts” functions needs to be immediately called because it’s an “async” function inside of a “useEffect” function. Send a “get” request to the URL “profile/:username/posts” where “username” is dynamic and the URL is in backticks. Do this by importing “useParams” from “react-router-dom” and at the beginning of your function set a constant variable “{username}” in brackets and set to “useParams().”

Now hollow out the “username” part of the URL for “${username}”. Log “response.data” to the console to see what the server sending back. Now make “setIsLoading” to false and set “post” to “response.data.”

useEffect(() => {
  async function fetchPosts() {
  try {
    const response = await Axios.post(a, b)
    setPosts(response.data)
  } catch {

   }
  }

  fetchPosts()
}, [])

Every time you refresh the browser you should see a loading text for a split second.

Now loop through raw data to display actual posts. Copy first anchor element then delete all three. Setup a “posts.map()” function. Give a “key” prop to the anchor element for “post._id” and make hardcoded data dynamic.

return(
  <div className="listGroup">
    {posts.map((post) => {
      return (
        // paste in div with anchor tag here
        // anchor tags need a "key" prop added
      )     
    })}
  </div>
)

Note: React does not honor white space between elements or components, so to fix this find where the title ends and add in curly brackets quotes and a space.

Inside your map function create a constant variable “date” and set it equal to a new date object and also create a constant variable “dateFormatted” to clean up the way the date object looks.

const date = new Date(post.createdDate)

const dateFormatted = `${date.getMonth() + 1} / ${date.getDate()} / ${date.getFullYear()}`

Note: Remember months are zero based.

We are using client-side routing for the links so import, or get easy access to, “{Link}” from “react-router-dom” and change all anchor elements to “Link” and update any “hrefs” to be “to.”


Ch. 43 – Make Single Post Screen Actually Loads the Real Content:

In “ViewSinglePost.js” file create two piece of state using “useState” called “[IsLoading, setIsLoading]” and set it to true and another piece of state called “[post, setPost]” with no initial value. Have an if statement that says “(isloading)” “return” a “Page” component with a prop of “title” set to “…” and inside a div with “Loading…”

Use “axios” to get information from the server and when it comes back update your state. Duplicate “useEffect,” from previous lesson and update the URL to “blank” and remove last segment of post. Also change “username” to “post.”

useEffect(() => {
  async function fetchPost() {
  try {
    const response = await Axios.get(`/post/${id}`)
    setPost(response.data)
    setIsLoading
  } catch(e) {
    console.log(e)
   }
  }

  fetchPost()
}, [])

So you’re going to need to import “{useParams}” from “react-router-dom” and set a constant variable of “{id}” equal to “setParams()” but no parentheses to actually call it?

Rename the “fetchPosts” function to be singular and import “Axios” from “axios” at the top of the file. Update the JSX to pull in the relevant content, “{post.author.avatar}, {post.author.username} etc.”

Recycle code for the date from the previous lesson.

Fix the page title shown in the browser tab by updating the “title” prop on the “Page” tag to be “{post.title}” and then jump into “Page.js” file and update the “useEffect” to update anytime the page title changes by adding “props.title” to the dependency array.

Back in “ViewSinglePost.js” file update anchor elements to “Link” and swap “hrefs” to “to,” and make sure the URL paths are accurate.


Ch. 44 – Animated Loading Icon

Instead of just text that says “Loading…,” we’re going to create a reusable component that shows a spinning loading icon.

When testing we’re going to tweak our connection in the browser’s development tools in network throttling slow 3G. Create a new “LoadingDotsIcon.js” file and where you return JSX return an empty div with a “className” of “{dots-loading}.”

We don’t get into the CSS in the video but here it is:

/* Dots Loading Animation */

.dots-loading {
  margin: 0 auto;
  text-align: center;
}

.dots-loading::before,
.dots-loading::after {
  content: " ";
}

.dots-loading div,
.dots-loading::before,
.dots-loading::after {
  margin: 35px 5px;
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: #c4c4c4;
  opacity: 0;
}

.dots-loading::before {
  -moz-animation: opacitychange 1s ease-in-out infinite;
  -webkit-animation: opacitychange 1s ease-in-out infinite;
  -o-animation: opacitychange 1s ease-in-out infinite;
  animation: opacitychange 1s ease-in-out infinite;
}

.dots-loading div {
  -moz-animation: opacitychange 1s ease-in-out 0.33s infinite;
  -webkit-animation: opacitychange 1s ease-in-out 0.33s infinite;
  -o-animation: opacitychange 1s ease-in-out 0.33s infinite;
  animation: opacitychange 1s ease-in-out 0.33s infinite;
  -webkit-animation-fill-mode: infinite;
  animation-fill-mode: infinite;
}

.dots-loading::after {
  -moz-animation: opacitychange 1s ease-in-out 0.66s infinite;
  -webkit-animation: opacitychange 1s ease-in-out 0.66s infinite;
  -o-animation: opacitychange 1s ease-in-out 0.66s infinite;
  animation: opacitychange 1s ease-in-out 0.66s infinite;
  -webkit-animation-fill-mode: infinite;
  animation-fill-mode: infinite;
}

@keyframes opacitychange {
  0%,
  100% {
    opacity: 0;
  }

  60% {
    opacity: 1;
  }
}

@-webkit-keyframes opacitychange {
  0%,
  100% {
    opacity: 0;
  }

  60% {
    opacity: 1;
  }
}

Now in the “ViewSinglePost.js” file import the “LoadingDotsIcon.js” file and find the if statement where it says if “is loading” is true and swap out the div that says “Loading…” for a self-closing “<LoadingDotsIcon />” component. Do the same thing in the “ProfilePosts.js” file.

Now that we know how to throttle internet speeds it’s easy to see the issue of what happens when something is loading and a user clicks back to the home screen. We get an error in the console that we “can’t perform a React state update on an unmounted component.”

Ch. 45 – Cleaning Up After Ourselves (useEffect)

We fix the problem introduced at the end of the previous chapter. In “ViewSinglePost.js” and any component when you use an asynchronous request within the function that you give “useEffect” create a cleanup function, (also known as a teardown function).

You do this by returning an arrow function and inside of the function you cancel your “axios” request by giving your request a cancel token to identify your request. Just inside your “useEffect,” and above your async function, create a constant variable named “ourRequest” and set it equal to “axios.CancelToken.source().” Now in your “axios.get() ” request pass it a second parameter after the URL that is an object of “{cancelToken: ourRequest.token}.” In your cleanup function have “ourRequest.cancel().”

useEffect(() => {

  const ourRequest = Axios.cancelToken.source()

  async fetchPost() {

  try {
    
    const response = await Axios.get("url", {cancelToken: ourRequest.token})
    setPost(response.data)
    setIsLoading(false)
  
  } catch(e) {
    
    console.log(e)
    
    }
  }
  
  fetchPost()

  // teardown function

  return () => ourRequest.cancel()

}, [])

Note: if you’re sending a “POST” request instead of a “GET” request, the cancel token object would be the third argument and not the second, (since you send a data object as the second argument for a “POST” request).

Go into “Profile.js” and “ProfilePosts.js” files to make this same change to your http requests.

This “cleaning up” idea is not just limited to asynchronous network requests, It’s much bigger than that and the idea that if a component is no longer being used we clean up after ourselves. Another example, would be adding keyboard event listener to close a full screen search overlay or modal when the escape key is hit. When the overlay is closed we want to remove the listener.


Ch. 46 – Markdown in React

**thisBold** // makes bold

*thisItalicized* // italicized

### This is an h3 title

Google for “markdown cheat-sheet” wiki on GitHub, (here), to see all the different things you can do in Markdown.

Markdown has nothing to do with React. We want users to be able to use Markdown in there posts so we need to install “react-markdown” and import into “ViewSinglePost.js” file.

Leverage the package with self-closing “<ReactMarkdown />” tags with a “source” prop set to “{post.body}” and an “allowedTypes” prop to limit the type of elements you want to allow, (optional step).

import ReactMarkdown from "react-markdown"

<ReactMarkDown children={post.body} allowedTypes={[ "paragraph", "strong", "emphasis", "text", "heading", "list", "listItem" ]}/>

Note that, due to an update, we use the “children” prop instead of a “source” prop but you may still see “source” throughout the code.