Projects People Resources Semesters Blog About
🥪

Going Full-Stack (Profile Generator)

Author:

Last Updated: 1 January, 2023

📊 Table of Contents

⚠️
This walkthrough extends our earlier React Profile Generator (
🦋
Building Your First Site (Profile Generator)
) and Express Roster API (
Building A RESTful API (Oasis Roster)
). Please review both of these and have them running successfully on your computer before continuing.

🥅 The Goal

We’ll be connecting our React frontend and Express backend to create a full-stack web app. This will allow us to dynamically fetch our entire Oasis roster and render all member’s profile cards on the frontend.

Here’s an example of what this would look like once complete with 2 members on Oasis’ roster:

📦 Setup

To get started, it’s recommended to move both your frontend Profile Generator and backend Oasis Roster API directories into a new parent directory (oasis-starter in the below example). These should be the only contents in this directory:

oasis-starter/
├─ backend/
│  ├─ ... all backend files ...
├─ frontend/
│  ├─ ... all frontend files ...
Note: the names of these directories are arbitrary and up to you. However, always make sure to name files and directories in a manner that meaningfully reflect their contents and purpose.

Now, open up this new parent directory in VS Code and the terminal. All of your files—both frontend and backend—will now be under one roof. This strategy is referred to as a monorepo (short for “single repository”).

We’ll mainly be focusing on enhancing our frontend to connect to the backend. cd into your frontend directory within the terminal.

📌
As a reminder, you can launch a development server for your frontend by running npm start within your frontend directory. Similarly, your backend can be started by running npm start in your backend directory. By default, the frontend will hot reload changes automatically while the backend will require restarts.

🎞️ Creating a Gallery

Let’s start by building a new component to render our roster as a gallery. Create a new file within your frontend/src directory named Gallery.js. For now, we’ll use our Output React component to render a few (static) Oasis members:

// src/Gallery.js
import Output from "./Output";

export default function Gallery() {
  return (
    <div> {/* Wrapper element to contain all of Oasis members */}
      <Output
        photo="https://course.ccs.neu.edu/cs2500/_custom/img/assistant/andersonf.jpg"
        name="Frank Anderson"
        title="Program Coordinator"
        fact="Is neither totally left handed nor totally right handed."
      />

      <Output
        photo="https://course.ccs.neu.edu/cs2500/_custom/img/assistant/_jsella.jpeg"
        name="Jay Sella"
        title="Mentor"
        fact="I listened to over 2,500 artists across 62 genres last year."
      />

      {/* As many others as you'd like! */}
    </div>
  );
}

Even though we’ve created this, it won’t actually show up anywhere on our browser yet. That’s because we are not rendering the new Gallery component anywhere. We can do this by updating App.js:

// src/App.js
// other imports ...
import Gallery from "./Gallery";

export default function App() {
  // state variables ...

  return (
    <div>
      {/* Header, Form, and Output components ... */}

      <Gallery />
    </div>
  );
}

🔌 Connecting the Backend

While we do now have a gallery, it’s data is static and not being pulled from our backend roster. Let’s change that!

Install Axios

GET All Members

With that ready to go, you can now start writing the actual HTTP request. In Gallery.js, create a new getMembers() function to fetch all members from our backend roster:

function getMembers() {
  axios
    .get("http://localhost:8000/members") // GET the members from this endpoint
    .then(({ data }) => {
      // do this if successful ...
    })
    .catch((err) => {
      // do this if there's an error ...
    });
}
Any JavaScript logic placed within the .then() will only run if the HTTP request was successful. Similarly, logic inside the .catch() block will only run if the request failed (eg, an invalid URL).

Next, we need a way to call this function. It should only run the first time our Gallery component is rendered. We can do this by using the useEffect hook from React:

useEffect(() => {
  getMembers(); // fetch all members
}, []); // only fetch on first render

👉
To confirm that both of these are working, you can log the value of data to your browser’s console within the .then() block: console.log(data). For more about the console and how to access it, see
Console & Inspector
Console & Inspector
.

Before we can render this member roster, we need to store it. We’ll use React’s useState hook, similar to how form field values and the showOutput boolean are handled in App.js. A single state variable containing an array of all members will suffice for our use case:

const [members, setMembers] = useState([]); // initialize `members` state to an empty array

Putting these pieces together, you can fetch the roster as soon as the Gallery component is rendered and store the results in your members state array:

// src/Gallery.js
import axios from "axios"; // import Axios
import { useEffect, useState } from "react"; // import useEffect and useState

import Output from "./Output";

export default function Gallery() {
  const [members, setMembers] = useState([]); // initialize `members` state array

  function getMembers() { // define getMembers() function
    axios
      .get("http://localhost:8000/members") // fetch the data
      .then(({ data }) => {
        setMembers(data); // store the members in state if successful
      })
      .catch((err) => {
        console.error(err); // log an error to the browser console if there's an error
      });
  }

  useEffect(() => {
    getMembers(); // get the members the first time this component renders
  }, []);

  return (
    <div>
      {/* JSX elements to render */}
    </div>
  );
}

This is now almost complete! The only piece left is to replace the static profiles with those fetched and stored in state.

Render from State

Within the return block of your Gallery component, and inside the <div> HTML tag, iterate through each element of your members state array and render the appropriate Output for each of them:

// src/Gallery.js
// imports ...

export default function Gallery() {
  // state, getMembers(), useEffect() ...

  return (
    <div>
      {members.map((m) => (
        <Output
          key={m.id}
          photo={m.photo}
          name={m.name}
          title={m.title}
          fact={m.funFact}
        />
      ))}
    </div>
  );
}
💡
Where’d that key property come from? React provides this out-of-the-box for all JSX elements. In fact, all JSX elements directly inside a map() always need keys! These keys tell React which array item each component corresponds to for matching later. This is important if array items can move (eg, sorting), be inserted, or be deleted. A unique key helps React infer exactly what occurred and make the DOM tree correctly. Here, we are using the unique id from each of our members returned from the API. More on the DOM tree:
HTML
HTML
. Official React docs about keys and rendering lists:

🧨 Taking This Further

You can improve the user experience and robustness of this component in a few ways. Here are some ideas based on what you’ve learned so far:

  • style the roster
  • show a message while the data is loading
  • surface any errors to the user rather than just sending them to the browser console (most people don’t look at this!)
  • display a custom message if the roster is empty
  • create a few buttons that allow the user to sort members (eg, by name or title)
  • build out user-specific profile pages (using the second GET API endpoint from your backend)
  • add members to the roster when the Profile Generator form is submitted (after hooking up a database to the backend and creating a POST route)
  • anything else!