ReactJS-The Beginner Master Class
  • React In the Beginning
    • Lesson 1 - Demo: Build a Simple React App - Fast
    • Lesson 2 - HTML Review
    • Lesson 3 - CSS Review
    • Lesson 4 - Modern JavaScript (JSX) Patterns
    • Lesson 5 - Set up Dev Environment
    • Hands-on Practice
  • React Fundamentals
    • Lesson 1 - Understanding Old vs New Way of Building Web Apps - SPAs
    • Lesson 2 - Motivation for Using React as the Solution to Vanilla JS
    • Lesson 3 - What is ReactJS - How it Works
    • React Code Along from Scratch
    • Lesson 4 - Create and Run a React Project with Vite - Full Overview
    • Lesson 5 - Create Hook by State Management in React
    • Lesson 6 - React Project File and Folder Walkthrough
    • Lesson 7 - JSX and How React Treats the DOM & JSX Compilation(by Babeljs) - Overview
    • Lesson 8 - Understanding the Main Files - App.jsx and main.jsx
    • Lesson 9 - Props and One-Way Data Flow - Overview
    • Lesson 10 - Google Bookshelf App - Ver 1.0
    • Hands-on Practice I
    • Hands-on Practice II
  • React State and Styling
    • Lesson 1 - Pulling Book Data from a Different Data File
    • Lesson 2 - Overview of How State Works in React
    • Lesson 3 - RandomQuote App
    • Lesson 4 - Lifting State Up - React Pattern Overview
    • Hands-On - Simple Counter
  • Forms and Interactivity - Grocery List App
    • Lesson 1 - Setup a Simple Form and Input
    • Lesson 2 - Build Form Profile App Using Multi-input Form Data
    • Hands-on : Build a Grocery List App
  • Connecting to the Backend - Consuming APIs - UseEffect Hook
    • Lesson 1 - Connecting to the Back End - Understanding Side Effects, Hooks and useEffect - Overview
    • Lesson 2 - Fetching Data from the Backend the Right Way with useEffect Hook
    • Lesson 3 - Setting Up Loading State
    • Hands-on :Use Dependency Array and Adding Values that Control Side Effects
  • Solo Project 1
  • RESTful APIs :Build a BookSearch App -Ver 2.0
    • Lesson 1: Build and test RESTful APIs with Postman
    • Lesson 2 - BookShelf App Structure
    • Lesson 3 - Create NavBar.jsx Component
    • Lesson 4 - Create Footer Component
    • Lesson 5 - Create BookList.jsx Component
    • Lesson 6 - Create BookCard.jsx Component
    • Lesson 7 - Creating Custom Hooks - useBooks and api-client
    • Lesson 8 - Controlling Network Activities in React with AbortController
    • Lesson 9 - Show Book Details in a Modal - Working
    • Lesson 10 - Bookshelf App Summary
  • Multi-Page Applications (MPAs)
    • Build a Multi-Page React Application
    • Multi-Page React Application
    • Hands-on Practice
  • Backend Frameworks-NEXT.JS
    • Migrating from React to Next.js
    • Lesson 1: Key Concepts of NodeJS and Express for Backend Web Development
    • Lesson 2: How to set up a new Next.js project
    • Lesson 3: How to create Layouts and Pages
    • Hands-on Practice 1
    • Hands on Practice 2
      • New Project & Folder Structure
      • File-Based Routing
      • Server vs Client Components & Router Hooks
      • Start On The Navbar
      • Navbar Links, Dropdowns & React Icons
      • Active Links & Conditional Rendering
      • Homepage Components
      • Properties Page
      • Property Card Dynamic Data
      • Home Property Listings
      • Custom Not Found & Loading Pages
  • Git and GitHubs
    • Git Installation
    • Git Commands
    • GitHub Repository
    • Hands-on Practice
  • Database in Application
    • React Supabase CRUD
    • Hands-on: Note Taking App
  • NoSQL Database
    • Installing MongoDB Community Edition
    • System Env Path Setting
    • How to use MongoDB Shell
    • How to Connect and Use Mongosh in VS Code via MongoDB Extension
    • MongoDB Guide
  • Solo Project 2
  • Deployment and Web Hosting
    • Lesson 1. React+Vite+TS+Shadcn Project
    • Lesson 2. Deploying a React Vite App to Vercel from Vercel CLI
    • Lesson 3 Connecting to GitHub Repo and Automate Deployment
  • Solo Project 3
  • Final Term Project
    • Level 1 Team Project
    • Level 1 Team Project
    • Level 1 Team Project
    • Level 1 Team Project
    • Level 2 Team Project
    • Level 2 Team Project
    • Level 3 Team Project
    • Level 3 Team Project
Powered by GitBook
On this page
  • Stock Tracker App
  • 1. Prerequisites
  • 2. Project Setup
  • 3. Define Static Data (stockData.js)
  • 4. Create the Stock Component (Stock.jsx)
  • 9. Improve App
  1. Final Term Project

Level 2 Team Project

PreviousLevel 1 Team ProjectNextLevel 2 Team Project

Last updated 1 month ago

Stock Tracker App

This guide provides a step-by-step procedure to build a Stock Tracker application using React and Vite, with dynamically-generated stock data. College students can follow along to implement and understand each part of the app.


1. Prerequisites

  • Node.js and npm installed on your machine.

  • Basic knowledge of React (components, props, state, hooks).

  • A code editor (e.g., VS Code).


2. Project Setup

  1. Initialize a new Vite + React project:

    npm create vite@latest stock-tracker-app -- --template react
    cd stock-tracker-app
    npm install
    npm run dev
  2. Clean up default files:

    • Remove unnecessary boilerplate (e.g.src/logo.svg).

    • Create the following structure inside src/:

      src/
      ├─ components/
      │  └─ Stock.jsx
      ├─ data/
      │  └─ stockData.js
      ├─ images/
      │  ├─ amazon.svg
      │  ├─ apple.svg
      │  ├─ google.svg
      │  └─ app-logo.svg
      ├─ App.jsx
      └─ style.css

3. Define Static Data (stockData.js)

In src/data/stockData.js, export an array of stock objects:

// src/data/stockData.js
export default [
  { stockName: "AAPL", logo: "./images/apple.svg",  currentPrice: 153.83, prevClosingPrice: 151.03 },
  { stockName: "META", logo: "./images/meta.svg",  currentPrice: 184.91, prevClosingPrice: 185.25 },
  { stockName: "GOOGL",logo: "./images/google.svg",currentPrice: 95.13,  prevClosingPrice: 92.65 },
  { stockName: "AMZN", logo: "./images/amazon.svg",currentPrice: 93.75,  prevClosingPrice: 94.91 },
  { stockName: "MSFT",logo: "./images/microsoft.svg",currentPrice: 256.85, prevClosingPrice: 256.85 }
];

4. Create the Stock Component (Stock.jsx)

This component will:

  • Receive a stock object via props

  • Destructure values from stock (stockName, logo, currentPrice, prevClosingPrice)

  • Compute:

    1. numericalChange = currentPrice - prevClosingPrice

    2. rateChange = (numericalChange / prevClosingPrice) * 100

    3. arrow = "⬆" / "⬇" / "▬" based on change sign

    4. colorClass = "green" / "red" / undefined

  • Format both change values to two decimal places using .toFixed(2)

// src/components/Stock.jsx
import React, {useState} from 'react';

export default function Stock({ stock }) {
  const { stockName, logo, currentPrice, prevClosingPrice } = stock;

  // 1) Compute numerical change and rate change
  const numericalChange = parseFloat(
    (currentPrice - prevClosingPrice).toFixed(2)
  );
  const rateChange = parseFloat(
    ((numericalChange / prevClosingPrice) * 100).toFixed(2)
  );

  // 2) Determine arrow and color class
  const arrow = numericalChange > 0
    ? '⬆'
    : numericalChange < 0
    ? '⬇'
    : 'â–¬';

  const colorClass = numericalChange > 0
    ? 'green'
    : numericalChange < 0
    ? 'red'
    : undefined;

  return (
    <div className="stock-container">
      {/* Price change section */}
      <div className={colorClass}>
        <p>{arrow}{numericalChange.toFixed(2)}</p>
        <p><span>{rateChange.toFixed(2)}%</span></p>
      </div>

      {/* Stock logo */}
      <div>
        <img src={logo} alt={`${stockName} logo`}/>
      </div>

      {/* Stock symbol/name */}
      <div>
        {stockName}
      </div>

      {/* Current price */}
      <div>
        <p>{currentPrice.toFixed(2)}</p>
        <p>USD</p>
      </div>

      {/* Previous closing price */}
      <div>
        <p>Prev Close: {prevClosingPrice.toFixed(2)}</p>
      </div>
    </div>
  );
}

Note: Using .toFixed(2) ensures all numbers are represented with two decimal places, e.g., 5 → 5.00, 52.1 → 52.10.

5. Update the Main App (App.jsx)

  1. Import your data and component.

  2. Maintain state (currentData) to allow dynamic updates.

  3. Map through currentData, passing each stock as a prop to <Stock />.

  4. (Optional) Simulate live price updates using setInterval inside useEffect.

// src/App.jsx
import React, useState from 'react';
import stockData from './data/stockData';
import Stock from './components/Stock';
import './style.css';

export default function App() {
  const [currentData, setCurrentData] = React.useState(stockData);

  React.useEffect(() => {
    const id = setInterval(() => {
      setCurrentData(prev =>
        prev.map(s => ({
          ...s,
          currentPrice: +(
            Math.random() > 0.5
              ? s.currentPrice + Math.random() * 20
              : s.currentPrice - Math.random() * 20
          ).toFixed(2)
        }))
      );
    }, 4000);

    return () => clearInterval(id);
  }, []);

  return (
    <div>
      <header>
        <img className="app-logo" src="./images/app-logo.svg" alt="Logo" />
        <h1><span>Stock Tracker</span></h1>
      </header>

      <div className="wrapper">
        {currentData.map(stock => (
          <Stock key={stock.stockName} stock={stock} />
        ))}
      </div>
    </div>
  );
}

6. Styling (style.css)

Use the provided CSS to layout the grid, color-code changes, and style your app. Ensure your className attributes match those in the CSS (e.g., .green, .red, .stock-container).

// Some code
* {
	box-sizing: border-box;
}

body {
	margin: 0;
	display: flex;
	justify-content: center;
	background: #e3f1e9;
	background-image: url("images/hans-eiskonen-wn57cSQ7VzI-unsplash.jpg");
		background-size: cover;
		background-attachment: fixed;
		background-position: 100% 0%;
	color: #a59c9c;
	position: relative;
}

@media (min-width: 500px) {
	body {
		justify-content: flex-end;
		margin-right: 100px;
	}
}

.wrapper {
	width: 400px;
	border-radius: 10px;
	display: flex;
	flex-direction: column;
	gap: 50px;
}

img {
	filter: invert(36%) sepia(16%) saturate(1091%) hue-rotate(267deg) brightness(88%) contrast(92%);
}

header {
	height: 65px;
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 15px;
	background: #131c24;
	color: #8dac9b;
	position: fixed;
	width: 100vw;
	left: 0;
	z-index: 10;
}

header img {
	height: 40px;
	filter: invert(49%) sepia(14%) saturate(945%) hue-rotate(176deg) brightness(120%) contrast(88%);
}

h1 {
	font-size: 28px;
}

h1 span {
	color: #CFEBDF;
	text-shadow: 0px 2px 2px black;
}

.green {
	color: #317B22
}

.red {
	color: #DA4167
}

.stock-container:first-of-type {
	margin-top: 150px;
}

.stock-container:last-of-type {
	margin-bottom: 40px;
}

.stock-container {
	width: 400px;
	height: 300px;
	box-shadow: 1px 4px 4px 2px #212529;
	border-radius: 10px;
	display: grid;
	grid-template-columns: 1fr 1fr 1fr 1fr;
	grid-template-rows: 1fr 1fr .5fr 1fr 1fr 1fr;
	grid-auto-flow: row;
	grid-template-areas:
		"logo logo price-change price-change"
		"logo logo price-change price-change"
		"name name price-change price-change"
		"current-price current-price current-price current-price"
		"current-price current-price current-price current-price"
		"last-price last-price last-price last-price";
}



.stock-container div:nth-of-type(2) {
	grid-area: logo;
	display: flex;
	justify-content: center;
	align-items: flex-end;
	background: #dee2e6;
	border-top-left-radius: 10px;
}

.stock-container img {
	width: 60px;
	margin-bottom: 5px;
}

.stock-container div:nth-of-type(3) {
	grid-area: name;
	text-align: center;
	background: #dee2e6;
	margin-top: -10px;
}

.stock-container div:nth-of-type(1) {
	grid-area: price-change;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	gap: 0;
	background: #ced4da;
	border-top-right-radius: 10px;
}

.stock-container div:nth-of-type(1) p:nth-of-type(1) {
	font-size: 30px;
	margin: 0;
}

.stock-container div:nth-of-type(1) p:nth-of-type(2) {
	font-size: 15px;
	margin-left: 35px;
	margin-top: 0;
}

.stock-container p:nth-of-type(1) span {
	font-size: 15px;
	display: inline-block;
}

.stock-container div:nth-of-type(4) {
	grid-area: current-price;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	background: #49505799;
	color: #e9ecef;
	margin: 0;
}

.stock-container div:nth-of-type(4) p:nth-of-type(1) {
	margin: 0;
	font-weight: 700;
	font-size: 40px;
}

.stock-container div:nth-of-type(4) p:nth-of-type(2) {
	font-size: 15px;
	margin: 0;
}

.stock-container div:nth-of-type(5) {
	background: #adb5bd;
	grid-area: last-price;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	font-size: 15px;
	color: black;
	height: 100%;
	border-bottom-right-radius: 10px;
	border-bottom-left-radius: 10px;

}

7. Assets

Place your SVG files in src/images/ and reference them with relative paths in stockData.js and App.jsx.


8. Run & Test

  1. Start development server: npm run dev

  2. Open http://localhost:5173 in your browser.

  3. Verify:

    • Stock cards render dynamically from stockData.js.

    • Numeric and percentage changes update correctly.

    • Color and arrows reflect gains, losses, or no change.

    • Live price updates (if implemented).

  4. Output:

9. Improve App

We noted that prevClosingPrice is not updated at every 4 sec as currentPrice is updated. Let's improve the code by modifying App.jsx and Stock.jsx to reflect the change accordingly.

To ensure that prevClosingPrice is properly synchronized with currentPrice and can be updated accordingly, you need to modify both App.jsx and Stock.jsx. Here's how you can do it:

App.jsx

  1. Initialize prevData with the same data as currentData initially.

  2. Update prevData every time currentData is updated.

Here's the modified App.jsx:

// updated src/App.jsx
import React from 'react'
import stockData from './data/stockData'
import Stock from './components/Stock'
import './style.css'

export default function App() {
  const [currentData, setCurrentData] = React.useState(stockData)
  const [prevData, setPrevData] = React.useState(stockData) // Initialize with the same data as currentData

  React.useEffect(() => {
    const id = setInterval(() => {
      const updatedData = currentData.map((s) => ({
        ...s,
        currentPrice: +(
          Math.random() > 0.5
            ? s.currentPrice + Math.random() * 20
            : s.currentPrice - Math.random() * 20
        ).toFixed(2),
      }))

      setPrevData(currentData) // Update prevData before updating currentData
      setCurrentData(updatedData)
    }, 4000)

    return () => clearInterval(id)
  }, [currentData]) // Dependency on currentData to ensure it updates correctly

  return (
    <div>
      <header>
        <img className="app-logo" src="src/images/app-logo.svg" alt="Logo" />
        <h1>
          <span>Stock Tracker App</span>
        </h1>
      </header>

      <div className="wrapper">
        {currentData.map((stock, index) => (
          <Stock
            key={stock.stockName}
            stock={stock}
            prevStock={prevData[index]}
          />
        ))}
      </div>
    </div>
  )
}

Explanation

  • App.jsx:

    • prevData is initialized with the same data as currentData.

    • Every 4 seconds, prevData is updated to the current currentData before currentData is updated with new random prices.

    • The Stock component is now passed both stock and prevStock as props.

Stock.jsx

  1. Accept prevStock as a prop to get the previous stock data.

  2. Calculate the numericalChange and rateChange using both currentPrice and prevClosingPrice.

Here's the modified Stock.jsx:

// updated src/components/Stock.jsx
import React from 'react'

export default function Stock({ stock, prevStock }) {
  const { stockName, logo, currentPrice, prevClosingPrice } = stock
  
  // 1) Compute numerical change and rate change
  const numericalChange = parseFloat(
    (currentPrice - prevClosingPrice).toFixed(2),
  )
  const rateChange = parseFloat(
    ((numericalChange / prevClosingPrice) * 100).toFixed(2),
  )

  // 2) Determine arrow and color class
  const arrow = numericalChange > 0 ? '⬆' : numericalChange < 0 ? '⬇' : '▬'

  const colorClass =
    numericalChange > 0 ? 'green' : numericalChange < 0 ? 'red' : undefined


  return (
    <div className="stock-container">
      {/* Price change section */}
      <div className={colorClass}>
        <p>
          {arrow}
          {numericalChange.toFixed(2)}
        </p>
        <p>
          <span>{rateChange.toFixed(2)}%</span>
        </p>
      </div>

      {/* Stock logo */}
      <div>
        <img src={logo} alt={`${stockName} logo`} />
      </div>

      {/* Stock symbol/name */}
      <div>{stockName}</div>

      {/* Current price */}
      <div>
        <p>{currentPrice.toFixed(2)}</p>
        <p>USD</p>
      </div>

      {/* Previous closing price */}
      <div>
        <p>
          Prev Close:
          {/* {prevClosingPrice.toFixed(2)} */}$
          {prevStock.currentPrice.toFixed(2)}
        </p>
      </div>
    </div>
  )
}
  • Stock.jsx:

    • stock property is used to calculate numericalChange and rateChange.

    • prevStock property is used to update prevClosingPrice one step behind currentPrice.

    • The numericalChange and rateChange are displayed to reflect the change in price.

    • Both current and previous prices are shown for reference.

    This setup ensures that prevClosingPrice is always one step behind currentPrice, allowing you to accurately calculate and display the changes.