Level 2 Team Project
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
Initialize a new Vite + React project:
npm create vite@latest stock-tracker-app -- --template react cd stock-tracker-app npm install npm run dev
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
)
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
)
Stock
Component (Stock.jsx
)This component will:
Receive a
stock
object via propsDestructure values from
stock
(stockName, logo, currentPrice, prevClosingPrice)Compute:
numericalChange =
currentPrice - prevClosingPrice
rateChange =
(numericalChange / prevClosingPrice) * 100
arrow = "⬆" / "⬇" / "▬" based on change sign
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
)
Import your data and component.
Maintain state (
currentData
) to allow dynamic updates.Map through
currentData
, passing eachstock
as a prop to<Stock />
.(Optional) Simulate live price updates using
setInterval
insideuseEffect
.// 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. AssetsPlace your SVG files in
src/images/
and reference them with relative paths instockData.js
andApp.jsx
.

8. Run & Test
Start development server:
npm run dev
Open
http://localhost:5173
in your browser.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).
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
Initialize
prevData
with the same data ascurrentData
initially.Update
prevData
every timecurrentData
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 ascurrentData
.Every 4 seconds,
prevData
is updated to the currentcurrentData
beforecurrentData
is updated with new random prices.The
Stock
component is now passed bothstock
andprevStock
as props.
Stock.jsx
Accept
prevStock
as a prop to get the previous stock data.Calculate the
numericalChange
andrateChange
using bothcurrentPrice
andprevClosingPrice
.
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 calculatenumericalChange
andrateChange
.prevStock
property is used to updateprevClosingPrice
one step behindcurrentPrice
.The
numericalChange
andrateChange
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 behindcurrentPrice
, allowing you to accurately calculate and display the changes.
Last updated