Week 3Week 5

Week 4 - Data and Networking

~58 min read

Pre-Workshop

What's JSON?

JSON stands for JavaScript Object Notation. It is a text format that is language independent, but uses conventions that are familiar to programmers of the C-family of languages (C, C++, Java, JavaScript, etc.). It is a common format for data transfer because it is easy for humans to read and write, and it is easy for machines to parse and generate.

Understanding and using JSON is a key skill for any developer. JSON is used all over the place, from web APIs to configuration files. It's also a great way to store data for your own programs.

In JSON, we have two main types of "structures" that we can use to store data: arrays and objects.

JSON Arrays

Arrays
[
  "item1",
  "item2",
  "item3"
]

They work the same way as arrays in other languages, particularly JavaScript. They are a list of values, separated by commas, and surrounded by square brackets.

And JSON is a heterogenous data structure, meaning that we can store different types of data in the same array.

Arrays
[
  "item1",
  2,
  true
]

Keep in mind that not all languages support heterogenous arrays. For example, in Java, arrays can only store one type of data.

JSON Objects

So arrays are great for storing lists of data, but what if we want to store more complex data? That's where objects come in.

Objects
{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}

Objects in JSON are similar to objects in JavaScript. They are a collection of key-value pairs, separated by commas, and surrounded by curly braces. The key is always a string, and the value can be any type of data.

In JavaScript, objects can also have functions as values, but in JSON, the value must be a string, number, object, array, boolean, or null.

Here's something interesting:

  • we can store an array inside of an object
  • we can store an object inside of an array
  • we can store an array inside of an array
  • we can store an object inside of an object

JSON is a very flexible data structure, and we can nest arrays and objects as much as we want.

Nested
{
  "key1": [
    "item1",
    "item2",
    "item3"
  ],
  "key2": {
    "key3": "value3"
  }
}

Practical JSON

Let's have a realistic JSON data structure example. Let's say we want to store information about a person. We could do something like this:

Person
{
  "name": "John Doe",
  "age": 30,
  "hobbies": [
    "reading",
    "hiking",
    "coding"
  ],
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "state": "NY",
    "zip": "10001"
  },
  "contact": {
    "email": "john@email.com",
    "phone": "123-456-7890"
  },
  "isMarried": true,
}

What if we wanted to store multiple people? And maybe their kids as well?

People
[
  {
    "name": "John Doe",
    "age": 30,
    "hobbies": [
      "reading",
      "hiking",
      "coding"
    ],
    "kids": [
      {
        "name": "Jane Doe",
        "age": 5
      },
      {
        "name": "Jack Doe",
        "age": 3
      }
    ]
  },
  {
    "name": "Greg Heffley",
    "age": 20,
    "hobbies": [
      "wimpying",
      "reading",
      "diarying"
    ],
  }
]

Notice how we didn't have the kids array at all for Greg Heffley - this is still valid JSON! We don't conform to a strict schema, so we can have different data for each person. This is one of the benefits of JSON. But at the same time, you should be careful to make sure that your data is consistent - and check for fields that might be missing.

Want to check if your JSON is valid? Use JSONLint.

All of this is extremely important to today's workshop, because we will be working with JSON data from an API.

Working with Data

In the Pre-Workshop, we learned about JSON and how to work with it. Now, we will be using that knowledge to work with data from an API.

But first, let's talk about using JSON in our code itself. In /app/page.js (either outside and before the default component or inside of it), let's create a variable called person and assign it to a JSON object.

person
const person = {
  "name": "John Doe",
  "age": 30,
  "hobbies": [
    "reading",
    "hiking",
    "coding"
  ],
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "state": "NY",
    "zip": "10001"
  },
  "contact": {
    "email": "john@email.com",
    "phone": "123-456-7890"
  },
  "isMarried": false,
}

If you're on Chrome, I reccomend downloading the JSON Formatter extension. It will make it easier to read JSON in the browser. Sometimes it's hard to read JSON in the browser because it's all on one line, or it's condensed. This extension will format it for you.

I've made a component for us to use to display this data. (Purely for this example, not for our site) You can copy it from below and paste it into /app/page.js:

/app/page.js
function Person() {
  return (
    <div className="max-w-sm border border-primary-200 rounded-xl px-4 pb-4 pt-6 bg-gradient-to-bl from-secondary-100 to-white via-white md:hover:shadow-xl md:hover:scale-105 transition-all space-y-4">
      <div className="space-y-2">
        <h3 className="font-medium text-xl">Name</h3>
        <p className="flex items-center justify-start space-x-2 text-xs font-medium uppercase text-primary-600">
          # years old & ???
        </p>
        <p className="text-sm text-primary-500 underline">Address</p>
      </div>
      <div className="space-y-1">
        <h4 className="font-medium">Hobbies</h4>
        <ul className="flex flex-wrap gap-2 text-sm text-secondary-800">
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 1</li>
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 2</li>
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 3</li>
        </ul>
      </div>
      <div className="space-y-1">
        <h4 className="font-medium">Contact</h4>
        <ul className="font-mono text-sm">
          <li>random@email.com</li>
          <li>123-456-7890</li>
        </ul>
      </div>
    </div>
  );
}

Place it into <Home /> in /app/page.js:

/app/page.js
export default function Home() {
  const person = {
    name: "John Doe",
    age: 30,
    hobbies: ["reading", "hiking", "coding"],
    address: {
      street: "123 Main St",
      city: "New York",
      state: "NY",
      zip: "10001",
    },
    contact: {
      email: "john@email.com",
      phone: "123-456-7890",
    },
    isMarried: true,
  };

  return (
    <Container>
      <Person />
    </Container>
  );
}

Passing Data with Props

How do we get the data from the person variable into the component? We can use props! Props are very similar to HTML attributes. We can pass data from the parent component to the child component using props.

In <Person />, let's add a prop called person and set it to the person variable we created earlier.

/app/page.js
function Person({person}) {
  ...
}

and then, in <Home />'s <Person />, we can add the prop like this:

/app/page.js
...
<Person person={person} />
...

And you'll see that... nothing happens. That's because we haven't used the prop yet. Let's do that now.

In <Person />, let's replace all of the hard-coded data with the prop. For example, let's replace Name with {person.name}.

Person
<h3 className="font-medium text-xl">{person.name}</h3>

We use {} to tell React that we want to use JavaScript inside of JSX. We can use any JavaScript expression inside of {}. In this case, we are using the person prop and accessing the name property of it.

If you're confused about what's going on here, read this page about JavaScript in JSX with Curly Braces.

There are a few places we want to replace the hard-coded data with the prop right now. Here's a list of them:

  • The age of the person {person.age}
  • The phone number {person.contact.phone}
  • The email address {person.contact.email}
  • The address {person.address.street}, {person.address.city}, {person.address.state} {person.address.zip}

Notice how we can access nested properties of the person object using dot notation.

It should look something like this:

Person

function Person({ person }) {
  return (
    <div className="max-w-sm border border-primary-200 rounded-xl px-4 pb-4 pt-6 bg-gradient-to-bl from-secondary-100 to-white via-white md:hover:shadow-xl md:hover:scale-105 transition-all space-y-4">
      <div className="space-y-2">
        <h3 className="font-medium text-xl">{person.name}</h3>
        <p className="flex items-center justify-start space-x-2 text-xs font-medium uppercase text-primary-600">
          {person.age} years old & ???
        </p>
        <p className="text-sm text-primary-500 underline">
          {person.street} {person.city} {person.state} {person.zip}
        </p>
      </div>
      <div className="space-y-1">
        <h4 className="font-medium">Hobbies</h4>
        <ul className="flex flex-wrap gap-2 text-sm text-secondary-800">
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 1</li>
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 2</li>
          <li className="bg-secondary-100 px-2 py-0.5 rounded-full">Hobby 3</li>
        </ul>
      </div>
      <div className="space-y-1">
        <h4 className="font-medium">Contact</h4>
        <ul className="font-mono text-sm">
          <li>{person.contact.email}</li>
          <li>{person.contact.phone}</li>
        </ul>
      </div>
    </div>
  );
}

If you save the file and go back to the browser, you should see the data from the person variable displayed on the page. Try changing it and watch the component update! You didn't even have to change the component itself. Just the data.

Ternary Operators

You might've noticed that we didn't replace the ??? in the age line. That's because we need to do something special for booleans. We can't just put {person.isMarried} because that won't display anything. We need to use a ternary operator.

What's a ternary operator? It's a fancy way of saying "if-else statement". It's a way to write an if-else statement in one line. Here's an example:

const age = 30;

const isAdult = age >= 18 ? true : false;

This is the same as:

const age = 30;

let isAdult;

if (age >= 18) {
  isAdult = true;
} else {
  isAdult = false;
}

We can use ternary operators in JSX as well. Let's replace the ??? with a ternary operator.

Person
...
<p className="flex items-center justify-start space-x-2 text-xs font-medium uppercase text-primary-600">
  {person.age} years old & {person.isMarried ? "married" : "single"}
</p>
...

Notice how we can use ternary operators inside of JSX. We can use any JavaScript expression inside of {}.

We'll actually find ourselves using the ternary operator a lot in React. It's a very useful tool.

Maps and Arrays

We also have an array of hobbies in our person object at person.hobbies. How do we display that? We can use the map function to map each item in the array to a JSX element.

map is similar to a for each loop. It loops through each item in the array and does something with it. In this case, we want to create a <li> element for each item in the array. Here's an example:

map
const numbers = [1, 2, 3, 4, 5];

numbers.map((number) => {
  console.log(number);
});

So what would this look like in our component? Let's replace the hard-coded hobbies with the person.hobbies array.

Person
...
<ul className="flex flex-wrap gap-2 text-sm text-secondary-800">
  {person.hobbies.map((hobby) => (
    <li
      className="bg-secondary-100 px-2 py-0.5 rounded-full"
      key={hobby}
    >
      {hobby}
    </li>
  ))}
</ul>
...

Notice how we used parentheses instead of curly braces. This is because we are returning JSX/HTML. If we were returning a JavaScript expression, we would use curly braces.

What's that key prop? React requires that we have a unique key for each item in an array. We can use the item itself as the key, but if we have duplicate items, we can't do that. In this case, we don't have any duplicate items, so we can use the item itself as the key. But if we did have duplicate items, we could use the index of the item in the array as the key. (Not recommended, but it works)

Now, feel free to mess with the person variable we created, and watch the component update! Experiment with different data and see what happens. Try adding and removing fields and see how the component responds.

What's an API?

Now that we know how to work with data in our code, let's move on to working with data from the web. We will be using an API to get data from the web.

An API is an Application Programming Interface. It's a way for programs to communicate with each other. In this case, we will be using an API to get data from the web.

All of your favorite apps, websites, and devices use APIs!

  • Instagram uses an API to get your photos from your phone to their servers
  • YouTube uses an API to recommend videos to you
  • Google Maps uses an API to get directions
  • Your phone uses an API to send you notifications

APIs are everywhere! They are a key part of the modern web and modern technology.

This is a great time to make a commit! Make sure you add a commit message, and then push it to GitHub.

Working with a JSON API

Right now, we'll be working with a extremely basic JSON API. It's not a real API, but it will help us understand how APIs work. You can access it here: sample.json

In the upcoming workshops, we'll be using the Sanity.io API to get data from our CMS that we will be building. That one is much more extensive and realistic.

Hopefully, you should be able to read through it (it's in formatted JSON) and know what it does pretty quickly. It's a Dog API! It has a list of dogs, and each dog has a name, picture, age, and favorite toys.

Let's open /app/photos/page.js and work inside of there. I've already made a component for us to use to display the data. You can copy it from below and paste it into /app/photos/page.js:

/app/photos/page.js
function DogCard({ dog }) {
  return (
    <li className="border md:hover:scale-95 transition-transform border-primary-200 bg-gradient-to-bl from-secondary-100 to-white via-white rounded-xl p-4 flex items-start justify-start space-x-4">
      <img
        src={dog.pic}
        alt={dog.name}
        className="w-40 h-40 rounded-lg border object-cover border-primary-200"
      />
      <div className="space-y-4">
        <div className="space-y-1">
          <h3 className="text-lg font-medium">{dog.name}</h3>
          <p className="uppercase text-xs font-medium text-primary-600">
            {dog.age} year{dog.age === 1 ? "" : "s"} old
          </p>
        </div>
        <div className="space-y-2">
          <h4 className="text-sm font-medium">Favorite Toys</h4>
          <ul className="flex flex-wrap gap-2">
            {dog.favorite_toys.map((toy) => (
              <li
                key={toy}
                className="text-xs text-secondary-800 rounded-full px-2 py-0.5 bg-secondary-100"
              >
                {toy}
              </li>
            ))}
          </ul>
        </div>
      </div>
    </li>
  );
}

Make sure you take a look at this component, which combines a few things you've learned in this workshop:

  • Dot notation for JSON objects
  • Ternary operator
  • Maps and arrays

Let's also update <Photos />:

/app/photos/page.js
export default function Photos() {

  const dogs = [];

  return (
    <Container>
      <ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        {dogs.map((dog) => (
          <DogCard key={dog.name} dog={dog} />
        ))}
      </ul>
    </Container>
  );
}

We want to replace the [] in dogs with the data from the API. But how do we get the data from the API? We can use the fetch function.

Fetching Data

The fetch function is a built-in function in JavaScript that allows us to make HTTP requests. We can use it to get data from the web. Here's how that works:

  1. We call the fetch function with the URL of the API as the first argument
  2. We wait for the response from the API
  3. If the response is successful, we convert it to JSON
  4. We use the data from the API

This is an oversimplification of how fetch works. If you want to learn more, read the MDN Web Docs.

Creating a fetch Function

Let's create a function called getDogs that will fetch the data from the API.

/app/photos/page.js
async function getDogs() {
  const res = await fetch("https://md.rtsh.space/sample.json");

  if (!res.ok) {
    throw new Error("Failed to fetch dogs");
  }

  return res.json();
}

Let's break this down:

  1. We create an async function called getDogs. An async function is a function that returns a promise. A promise can either be resolved (kept) or rejected (broken). We can use await inside of an async function to wait for a promise to be resolved or rejected.
  2. We call the fetch function with the URL of the API as the first argument. This will return a promise.
  3. We use await to wait for the promise to be resolved or rejected. If the promise is rejected, we throw an error.
  4. We return the JSON data response from the API.
  5. We use res.json() to convert the response from the API to JSON.

Now, we can use this function to get the data from the API. Let's do that now.

/app/photos/page.js
export default async function Photos() {
  const dogs = await getDogs();

  return (
    <Container>
      <ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        {dogs.map((dog) => (
          <DogCard key={dog.name} dog={dog} />
        ))}
      </ul>
    </Container>
  );
}

Notice how we made the overall <Photos /> component async as well. This is because we are using await inside of it. We can only use await inside of an async function.

This is a great time to make a commit! Make sure you add a commit message, and then push it to GitHub.

(aside) Why we can do this

You don't have to read this section. I just added it for the sake of talking about some networking stuff

Networking is a (insert profanity here). In Next.js's old router, the Pages Router, if you wanted to get web data, you add several options:

  • Static Site Generation (SSG) - gets data at build time, so when you deploy your site, it will get the data and store it in the build. This is great for data that doesn't change often, like blog posts. The site only updates its web data every time you "publish" your site. If the data changes and you don't republish, the data won't change. Pro? It's fast. Con? It's not always up to date.
  • Server Side Rendering (SSR) - gets data at request time, so when you visit the site, it will get the data and store it in the server. This is great for data that changes often, like a dashboard. The site updates its web data every time you visit the site. Pro? It's always up to date. Con? It's slow.
  • Incremental Static Regeneration (ISR) - gets data at build time, but also updates the data every X seconds. This is great for data that changes often, but not too often, like your own personal website. The site updates its web data every X seconds. Pro? It's always up to date, but also fast. Con? It's not always up to date. But it will be after X seconds. (Personal favorite)
  • Client Side Rendering (CSR) - gets data at request time, but on the client side. The site updates its web data every time you visit the site. Pro? It's always up to date. Con? It's slow, and it's not great for SEO (Search Engine Optimization). Rather than do the networking on the server, it does it on the client (your device).

Visual Explanation and Comparison of CSR, SSR, SSG and ISR

Why so many options? Well, CSR and SWR were the only options to get data into specific components. But they were slow and bad for SEO. SSG, SSR, and ISR were faster and good for SEO, but they were only available for the entire page. So if you wanted to get data into a specific component, you had to use CSR or SWR.

But then, the App Router came with React 18, and a brand new feature: React Server Components

React Server Components

React Server Components allow you to write UI that can be rendered and optionally cached on the server.

The principles behind SSG, SSR, ISR, and CSR all still exist in some capacity or another, but have been slimmed down to where we can use them in specific components. Components that have async are considered Server Components whereas if they don't they are considered Client Components (optionally). (You can also force a component to be Client, which we'll do eventually.)

Server Components can be rendered on the server and optionally cached. This means that we can get data from the web and render it on the server, and then cache it. This is great for SEO and performance.

Client Components can be rendered on the client. This means that we can get data from the web and render it on the client. This sounds lackluster compared to Server Components, but there are some things we can't do with Server Components that we can do with Client Components. (Which we'll talk about later.)

Practice

This was a doozy of a workshop (to not only write, but just to take in.) There's a lot of information we covered, and it's 100% okay if you don't understand some of it. You'll get a few more chances to practice this stuff in the upcoming workshops.

Here are some options for practice:

  • Make a JSON object structure and make some components to display it
  • Think about an API you might want for your website
  • Come up with questions about this workshop and ask them in the Discord

Workshops