Home

Published

- 17 min read

Tessera Lab 3 - The Events Page

img of Tessera Lab 3 - The Events Page

Objective

In this lab, we’re going to roll up our sleeves and dive into creating an interactive Events Page using React, along with the APIs we cooked up earlier. We’ll spice things up by adding Chakra UI into the mix to make our interface not only user-friendly but also snazzy. We’ll also tackle some routing with React Router to make navigating our app a breeze. By the end of this session, we’ll all have hands-on experience linking our shiny frontend to our robust backend, giving us a taste of what full-stack development is all about. This lab is all about getting our feet wet with real-world app development, setting us up to tackle any software challenge that comes our way. Let’s get to it and build something awesome!

Tools!

Would this really be a Tessera lab if we weren’t installing new tools to help us along the way? We’ll be introducing some libraries that will help abstract some things away from us and, in general, let us focus on writing code and understanding the meat of what we’re building.

Chakra UI

In our lecture, we talked about react components. We talked about how we build components that nicely box up functionality and reuse them. When designing components, we need to keep in mind reusability as it immensely helps speed up our development, and keep our code concise and easy to use. Components, can be built up of other components. What we’ll be doing is using whats called a component library. A component library is a collection of reusable components that developers can utilize to build applications more efficiently. These libraries are designed to be flexible and modular, allowing for the easy assembly of complex user interfaces with consistent design and functionality. By using a component library, developers can streamline their development process, ensure design consistency across projects, and significantly reduce the time needed to implement standard UI elements and interactions.

Component libraries are sometimes developed internally at companies so that all products being built with their branding use the same type of buttons, colors, and patterns. What we’re going to be doing is using a powerful component library called Chakra UI. Chakra will provide us with buttons, forms, icons, and much much more. These are all styled very easily allowing us to focus more on functionality than style (although styling might be of interest to you! Let’s discuss!). Chakra also allows us to build accessible, and responsive apps. What this means is Chakra helps with making everything look nice on all devices (Phones, Tablets, Computers, The screen on your smart fridge, etc). To start, take a look at components that are availible to us on their component documentation. We’ll be using some of these to create our own components. Which ones look interesting to you? Have any ideas of how we could use some of them?

Lets install chakra. In your terminal, navigate to your frontend folder. Run the following command

   npm i @chakra-ui/react @chakra-ui/icons @emotion/react @emotion/styled framer-motion

Awesome, we’ll make good use of this soon.

React Router

Lets talk about routing. Traditionally, every webpage on a website required your computer to ask the server to send you a whole new page. This means every time you click on a link, your browser requests a whole bunch of files from the server and then your browser needs to evaluate all the HTML, CSS, and JavaScript all over again. What we want to do is use something called client-side rendering. Client-side rendering is a modern approach to web development where the HTML content of a webpage is dynamically generated in the user’s browser using JavaScript. Instead of receiving all of the content from the server pre-rendered, the browser downloads a minimal HTML document with JavaScript code that produces the HTML or UI elements.

We’ll be using React Router to help us with this. Go ahead and click around some of the documentation, tutorials, and examples. We’ll be using this to help with changing whats on the screen when we want to move to different webpages. Lets install some the libraries we need.

   npm install react-router-dom

GO!

Lets get building. Remember, we’re building full-stack - we’ll need to run our backend so we can communicate with our API’s we’ve built. As a reminder, run the following in a terminal inside your backend directory. Let’s keep this running while we develop the front end.

   python3 app.py

In another shell, navigate to the frontend directory and run the following to run the development server for our webapp.

   npm run dev

CORS

We need to make one tiny modification to our backend before we continue. Theres a real tricky system in place with modern web-browsers called CORS (Cross-Origin Resource Sharing). For our project, we dont need to understand CORS completley, just that its a security mechanism in place to stop one domain from requesting data from another domain. CORS is daunting to understand at first hence why we’ll gloss over it a bit in today lab. I urge you to read about it to understand why it exists and how it works.

In out backend directory, run the following command to install a package that will help us with the CORS handshake between our browser and the backend service

   pip3 install flask_cors

In your backend, find the following line

   # ...
app = Flask(__name__)
# ...

Now add the following import and the following line of code under the line. Should look like this.

   # ...All your other imports
from flask_cors import CORS
# ...
app = Flask(__name__)
CORS(app)
# ... 

Restart your backend if you need at this point.

The Code

For this first frontend lab, I will be providing a large amount of code. This is to get you familiar with how the code should look, and provide you examples for how to build react components. In future labs, I will provide you less and less code but guide you through steps you need to accomplish a certain goal or feature. Throughout any labs - I highly encourage you to add your own custom twist, rewrite code I provide, and go beyond what I have provided here. Front end development provides you a lot of customizability and you should find ways to make your Tessera project your own!

First lets delete some files we dont need. Under your /src directory, delete the App.css and index.css. Open up /main.jsx and delete the following line.

   import "./index.css"

Now create two folders under the \src directory. Call them components and pages. Here, we’ll create our components and pages (pages are actually nothing more than components!)

Open App.jsx and replace the code with the following.

   import React from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Navbar from './components/Navbar';
import EventsPage from './pages/EventsPage';
import EventDetail from './pages/EventDetail'; 

function App() {
  return (
    <ChakraProvider>
      <Router>
        <Navbar />
        <Routes>
          <Route path="/events" element={<EventsPage />} />
          <Route path="/" element={<Navigate to="/events" replace />} />
          <Route path="/events/:id" element={<EventDetail />} />
        </Routes>
      </Router>
    </ChakraProvider>
  );
}

export default App;

Under the pages directory, create EventsPage.jsx and copy over this code.

   import React, { useEffect, useState } from 'react';
import { SimpleGrid, Container } from '@chakra-ui/react';
import EventCard from '../components/EventCard';

function EventsPage() {
  const [events, setEvents] = useState([]);

  useEffect(() => {
    fetch('http://localhost:5000/events')
      .then(response => response.json())
      .then(setEvents)
      .catch(error => console.error('Error fetching events:', error));
  }, []);

  return (
    <Container maxW="container.xl" centerContent>
      <SimpleGrid columns={{ sm: 1, md: 2, lg: 3 }} spacing={10} py={5}>
        {events.map(event => (
          <EventCard
            key={event.event_id}
            id={event.event_id}
            name={event.name}
            date={event.date}
            location={event.location}
            imageUrl={'https://i.imgur.com/E5Wjs.jpeg'} 
          />
        ))}
      </SimpleGrid>
    </Container>
  );
}

export default EventsPage;

Under the pages directory, create EventDetail.jsx and copy over this code.

   import React from 'react';
import { useParams } from 'react-router-dom';

function EventDetail() {
  const { id } = useParams(); 

  return (
    <div>
      <h1>Event Detail Page</h1>
      <p>Showing details for event ID: {id}</p>
      <p>This is a placeholder page</p>
    </div>
  );
}

export default EventDetail;

Under the components directory, create Navbar.jsx and copy over this code.

   import React from 'react';
import { Box, Flex, Text, Button, Spacer } from '@chakra-ui/react';

function Navbar() {
  return (
    <Flex bg="blue.500" color="white" p="4" alignItems="center">
      <Box p="2">
        <Text fontSize="xl" fontWeight="bold">Tessera Events</Text>
      </Box>
      <Spacer />
      <Box>
        <Button colorScheme="blue" variant="outline">Profile</Button>
      </Box>
    </Flex>
  );
}

export default Navbar;

Under the components directory, create EventCard.jsx and copy over this code.

   import React, { useEffect, useState } from 'react';
import { Box, Image, Text, VStack, Heading, LinkBox, Button } from '@chakra-ui/react';
import { Link } from 'react-router-dom';

function EventCard({ id, name, date, location, imageUrl }) {
  const [timeLeft, setTimeLeft] = useState('');

  useEffect(() => {
    const updateTimer = () => {
      const eventDate = new Date(date).getTime();
      const now = new Date().getTime();
      const distance = eventDate - now;

      if (distance < 0) {
        setTimeLeft('Event has started');
        return;
      }

      const days = Math.floor(distance / (1000 * 60 * 60 * 24));
      const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
      const seconds = Math.floor((distance % (1000 * 60)) / 1000);

      // Update the timer text
      setTimeLeft(`${days}d ${hours}h ${minutes}m ${seconds}s`);
    };

    // Update the timer every second
    const timerId = setInterval(updateTimer, 1000);

    // Cleanup the interval on component unmount
    return () => clearInterval(timerId);
  }, [date]);

  return (
    <LinkBox as="article" w="full" borderWidth="1px" rounded="md" overflow="hidden" boxShadow="md">
      <VStack align="stretch">
        {imageUrl && (
          <Image borderRadius="md" src={imageUrl} alt={`Image for ${name}`} objectFit="cover" width="full" />
        )}
        <VStack align="stretch" p="4">
        <Heading size="md" my="2">{name}</Heading>
          <Text fontSize="sm">Date: {date}</Text>
          <Text fontSize="sm">Location: {location}</Text>
          <Text fontSize="sm" color="red.500">{timeLeft}</Text>
          <Button colorScheme="blue" mt="4" as={Link} to={`/events/${id}`}>
            Buy Tickets!
          </Button>
        </VStack>
      </VStack>
    </LinkBox>
  );
}

export default EventCard;

Naviate your web browser over to http://localhost:5173. Bask in the glory.

Notice a few things. We did not navigate to the /events page but we ended up there. How did that happen?

Explaination

Let’s walk through how this all works. Its imperative that you understand how all this code works so that we can build on this knowledge.

App.jsx

The App.jsx file is basically the grand central station of our React app. It’s where everything comes together: the look, the routes, and the main structure. We kick things off with ChakraProvider from Chakra UI, wrapping our entire app in it. This is cool because it lets us use Chakra UI’s themes and styles everywhere in our app without fussing about it in each component.

Inside, we’ve got Router, which is our navigation wizard from React Router. It keeps track of where we are in the app and helps switch between different pages without reloading the browser—pretty neat for keeping things smooth and fast.

Here’s the code where we set this up:

   <ChakraProvider>
      <Router>
        <Navbar />
        <Routes>
          <Route path="/events" element={<EventsPage />} />
          <Route path="/" element={<Navigate to="/events" replace />} />
          <Route path="/events/:id" element={<EventDetail />} />
        </Routes>
      </Router>
</ChakraProvider>

We’ve placed the Navbar right above Routes so it hangs out at the top of all our pages, giving us a consistent look and easy access to navigation. Speaking of Routes, this is where the magic happens: we’ve mapped URLs to components. For example, visiting /events brings up the EventsPage. We even have a smart redirect that whisks users from the root (/) straight to /events, because we’re all about getting to the good stuff fast.

EventsPage.jsx

The Events page is the meat and potatoes of our app right now. It shows a nice view of all the events currently available to us. But how does it know all this? Where is it getting the data from? Well, we need to learn about some hooks or special functions that React provides us to work with when we want to do dynamic things.

useState()

useState is a hook that lets you add React state to function components. Before hooks, state could only be used in class components, but useState changed the game. It lets you keep track of state variables in a function component. Each call to useState gives you two pieces: the current state value and a function that lets you update it.

Here’s how we use useState in our EventsPage:

   const [events, setEvents] = useState([]);

This line sets up events as a state variable that starts as an empty array. setEvents is the function we call when we want to update our events state — say, after fetching data from the backend.

useEffect()

useEffect is a function in React that you can use to run some code at specific times during your component’s life. Think of it as a way to say, “Hey, I want to do something now,” whether that’s right after your component shows up on the screen, when something changes, or right before your component goes away.

Heres how we used useEffect in the EventsPage

     useEffect(() => {
    fetch('http://localhost:5000/events')
      .then(response => response.json())
      .then(setEvents)
      .catch(error => console.error('Error fetching events:', error));
  }, []);

Lets break this down

  1. Fetch Data: We’re telling the browser to go get some data from ‘/events’. This is like asking someone to fetch a book from a library.
  2. Then Do Something With It: Once we have that data (the book), we do something with it. Here, we’re taking the data we got back and updating our list of events with it. This is like getting the book and then putting it on your shelf.
  3. Catch Any Errors: If something goes wrong (maybe the library was closed), we catch that error and print a message saying what went wrong. This is like noting down why you couldn’t get the book.

The code is pretty unique and different from other languages in this case. We’re using a built in function called fetch to make the API call and using .then chained together. This is called promise chaining. We’ll talk more about Promises in javascript at some point.

Whats up with the empty array as the second parameter to useEffect?

The empty array at the end of useEffect is like a rule that tells React when to run the code inside

   useEffect(() => {
  // Code to run
}, []);  // This empty array is important

By putting an empty array there, you’re telling React, “Run the code inside here just one time, right after the component first shows up on the screen.” If you didn’t put the empty array there, React would keep running that code over and over again every time something changes, which could lead to problems like too many requests to the server or slow performance. We’ll see this used another way soon

This use of useEffect ensures that your component sets up properly by fetching necessary data right after it first appears, making sure that your users see the latest and greatest info without having to do anything. It’s a powerful tool that helps manage what happens behind the scenes in your app effectively and at the right times.

Rendering

Now we know how we’re getting data, and we know how we’re storing the data in state. Once we have our data, it’s showtime. We use a SimpleGrid (from chakra, check the docs for more info) to lay out each event in its own little card — EventCard takes care of the details. These cards get all the info they need to display things like the event name, date, and location. We set up the grid to be responsive, so it adjusts how many columns there are depending on the size of the screen. Fancy, right?

Notice that we send in data to the EventCard using props! Remember, we learned about these and how data passing works between parent and child. Some things to think about, in the return object, we have some javascript code wrapped in {}. Inside we’re mapping over an array. We wrote one component for displaying events, but we can use it as many times as we like!

EventCard.jsx

The EventCard component is a neat little package that shows all the essential details about an event. It’s designed to be clear and informative, plus it has a nifty countdown timer that tells you how much time is left until the event starts.

Whenever we look at components we should look for what props are expected, any state that it manages, effects that can happen, and what is actually being returned from the function (i.e what is being rendered)

Lets start with props

   function EventCard({ id, name, date, location, imageUrl }) {

Looking at this we now have some data we can work with. Should look familiar to what we have in our database (and API)

State look simple too, we have a timeLeft variable that we’re using for the countdown on the event card

   const [timeLeft, setTimeLeft] = useState('');

The return block is interesting. Here, we’re taking a whole bunch of chakra ui components and creating a larger component that means something to us. I wont go into too much detail about each component here, we have the documentation for that. It is important however, to have a general understanding of how we took some components and created a big one.

useEffect() but more complex

This useEffect is a more complex usecase than the first one we did. Lets look at the code more closely with comments added to each line below.

   useEffect(() => {
  const updateTimer = () => {
    const eventDate = new Date(date).getTime(); // Convert the event date to milliseconds
    const now = new Date().getTime(); // Get the current time in milliseconds
    const distance = eventDate - now; // Calculate the difference

    if (distance < 0) {
      setTimeLeft('Event has started'); // If the event date is past, show this message
      return; // Stop the function if the event has already started
    }

    // Calculate days, hours, minutes, and seconds left
    const days = Math.floor(distance / (1000 * 60 * 60 * 24));
    const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((distance % (1000 * 60)) / 1000);

    setTimeLeft(`${days}d ${hours}h ${minutes}m ${seconds}s`); // Update the timeLeft state with the formatted string
  };

  const timerId = setInterval(updateTimer, 1000); // Set up a timer to run updateTimer every second

  return () => clearInterval(timerId); // Clear the timer when the component unmounts to prevent memory leaks
}, [date]); // The effect depends on the `date` prop, so it only reruns if `date` changes

Let’s look at some key points

  1. This code is to calculate and update the timer on the card as a countdown to when the event starts
  2. To update the text every minuite, we use javascript timers - read this and understand how we can use timers to run some code at a later time
  3. We run a calclation every second and update the text
  4. But theres an important thing happening here - the depdency array is NOT empty this time! This means that this whole function only runs when date changes. How many times does date change? How many times will this timer be set up
  5. Finally, note that for the firs time, we’re returning a function from useEffect. This function will run once. It will run once the component unmounts from the DOM. useEffect allows u to return a “Cleanup function” that runs to cleanup any risidual things once the component is no longer needed.

Phew… I know this is complicated. We’ll talk more about it in person!

The Navbar is something well talk more about soon. Open up the code and notice its pretty simple right now. Later on we’ll add functionality to this.

EventDetail is a placeholder. This is where we’ll show what seats are availible for purchase and move forward with the customer selecting and buying seats. Nothing here for now, just some placeholder text. Do note the props being passed in!

Assignments

Look… as cool as this is, you only learn by doing. So I have some challanges for you.

  1. Right now theres a rather happy panda being displayed as the image for every event. Modify your database schema to store a URL for the image for each event, update your API to return the URL, and modofy your front end code to use URL instead of the cheerful panda.
  2. Right now the Navbar has a Profile button. Its rather ugly. Make it look nicer. Maybe use icons? Checkout this page for how we can use Icons with Chakra components
  3. You may have noticed but the timer on each event card does not account for the time we stored in the database - only the date. Add functionality so it also takes into account the events time in its calculation
  4. Recall that our API supports both event date filtering and location filtering. Modify the fetch request to only show events in the future using the APIs filtering functionality. Here’s some docs on how to use fetch
  5. Build the rest of the app… just kidding😂… 🥺👉👈unless

See you in the next one!