How to Create Stocks WatchList React Component For Trading Platform

Nitish Kumar Singh

May 29, 2025

Learn how to make a simple and useful stocks watchlist in React. It's a great start if you're building your own trading app or just want to try something cool.

Hello developers, in this post we are going to create a Stocks WatchList React component that shows a list of stocks, renders price changes efficiently, and manages different user actions.

We can see the visual output of our component as shown in the image below.

In this post, we are not going to focus on all the features that we see in the above image like the search feature. We will just focus on things as listed below:

  • We will render a list of stocks and show their LTP price and price changes.
  • Render price changes very efficiently by following the per-row-rendering rule, which means we just render the row internally that is required to render.
  • We will show a toolkit of buttons that handle different actions like removing from watchlist, placing orders, feed visualization, and chart opening.
  • Wrap different parts of this component in React.memo to prevent unnecessary rendering.

Okay, let's get started with the code.

So below is the code of our main React component WatchList:

import React, { Component, useCallback, useEffect, useState} from 'react';
import { formatPrice, getChange } from './UTILS.js';
import { feedVisualizer } from './FeedVisualizer.js';
import { feeds } from './App.js';

export default class WatchList extends Component {
  stocks = new Map(); 
  componentDidMount(){
    feeds.sendMessage({what:"getWatchlistgStocks",isMarketTime:feeds.isMarketTime},({ok, data})=>{      
      if (ok) {
        data.forEach(stock => this.stocks.set(stock.token, stock));
        // if(feeds.isMarketTime) feeds.subscribe(data, this.onTick);
        this.setState({});
      }
    });
  }
  render() {    
    return (
      <div className='stocksC' style={{height:0,flex:1,position:"relative"}}>
        <div className='stocks'>{this.getStocksView()} </div>
        <SearchView click={this.searchItemClick}/>
      </div>
    );
  }
  getStocksView = ()=>{
    const jsx = [];
    this.stocks.forEach(stock => jsx.push(<Stock key={stock.token} stock={stock} click={this.stockClick}/>));
    return jsx;
  }
}

We have used a map to store the list of stocks instead of an array because it helps us to implement per-row-rendering.

Here, formatPrice and getChange are used for formatting, feedVisualizer is a global object to help in Feed Visualization through the FeedVisualizer component, and feeds is an object of the FeedSocket class that handles subscribing and unsubscribing to stock tokens, sending messages to the server, and getting results back.

In my project, the server already subscribes to tokens that are part of the user watchlist whether any client is connected or not during market time.

In the componentDidMount function, we get the list of watchlist stocks in the form of an array, set these stocks in the map, subscribe to get tick data during market time, and render these stocks.

We are passing the stock and a listener function for handling different stock actions to the Stock component, whose code is below:

const Stock = React.memo(({stock, click})=>{
  const [tick, setTick] = useState(stock.tick || {});
  const onClick = useCallback(event => click(event, stock),[stock,click])
  useEffect(()=>{
    stock.setTick = setTick;
    return ()=> delete stock.setTick;
  },[stock, setTick]);
  return (
    <div className='item' onClick={onClick}>
      <StockLeft stock={stock}/>
      <div style={{display:"flex",flexDirection:"column",alignItems:"end"}}>
        <span style={{fontSize:"12px",color:tick.last_traded_price>tick.close_price?"green":"red"}}>₹{formatPrice(tick.last_traded_price)}</span>
        <span style={{fontSize:"10px",fontWeight:"",color:"#5c5c5c",lineHeight:"12px"}}>{getChange(tick)}</span>
      </div>
      <StockActionBtn click={onClick}/>
    </div>
  )
});

In useEffect, we have our main logic used for per-row-rendering by storing a state function to the stock object. On every tick, we directly render this component externally.

I know that we should not call a state function externally and here we are setting a function created in useEffect on the stock object, but the current code works, so I have left it as it is. But you can change it.

We have wrapped up static parts of the Stock component in React.memo to avoid unnecessary computation and rendering. Their code is below:

const StockLeft = React.memo(({stock})=>(
  <div style={{display:"flex",flexDirection:"column"}}>
    <div style={{display:"flex",alignItems:'center',gap:"5px"}}>
      <span style={{fontSize:"13px"}}>{stock.title}</span>
      <span style={{background:"gainsboro",padding:"0 2px",fontSize:"10px",borderRadius:"2px"}}>{stock.exchange}</span>
      <span style={{background:"gainsboro",padding:"0 2px",fontSize:"10px",borderRadius:"2px",opacity:0.8}}>{`${stock.leverage}x`}</span>
      <span style={{fontSize:"13px"}}>{!stock.setTick && stock.price ? `₹${formatPrice(stock.price)}`:""}</span>
    </div>
    <span style={{fontSize:"12px",color:"gray"}}>{stock.company||"Relience Industries Limited"}</span>
  </div>
));

const StockActionBtn = React.memo(({click})=>(
  <div className='wlshb'>
    <div>
      <button onClick={click}><span className={`material-symbols-outlined`} style={{fontSize: "20px", color: "inherit",pointerEvents:"none"}}>delete</span></button>
      <button onClick={click}><span className={`material-symbols-outlined`} style={{fontSize: "20px", color: "inherit",pointerEvents:"none"}}>monitoring</span></button>
      <button onClick={click}><span className={`material-symbols-outlined`} style={{fontSize: "20px", color: "inherit",pointerEvents:"none"}}>candlestick_chart</span></button>
      <button onClick={click}><span className='bsBtn s'>S</span></button>
      <button onClick={click}><span className='bsBtn b'>B</span></button>
    </div>
  </div>
));

StockLeft and StockActionBtn only depend on the stock object and are not part of rapidly coming ticks.

Below is the code of the onTick function that is used for getting ticks of all watchlist stocks:

  onTick = (tick)=>{    
    const stock = this.stocks.get(tick.token);
    stock.tick = tick;
    stock.setTick?.(tick);
  }

This is the benefit of using a map. We quickly access the stock when a tick for this stock comes and update the price movement in the UI.

I have tested with 20 stocks and seen that ticks come at a time interval of 100 to 200 milliseconds.

So as the watchlist grows, the time interval of ticks decreases, therefore I have implemented this row-per-rendering. And I know it is a non-React pattern, but the thing is that it works.

Now it's time to handle user actions. All stock-related actions are handled by a single function whose code is below:

  stockClick = (event,stock) => {
    let type = event.target.nodeName;
    if(type==="BUTTON"){
      event.stopPropagation();
      type = event.target.textContent.trim();
      if(type==="monitoring") feedVisualizer.visualize(stock);
      else if(type==="candlestick_chart"){
        // show chart
      }else if(type==="B"){
        // Place Buy Order
      }else if(type==="S"){
        // Place Sell Order
      }else if(type==="delete"){
        const s = {...stock};
        s.list = s.list.replace(",Spikes,","");
        feeds.sendMessage({what:"saveStocks",stocks:[s]},(ok) => {
          if (ok) {
            stock.list = s.list;
            this.stocks.delete(s.token);
            if(feeds.isMarketTime) feeds.unsubscribe([stock],this.onTick);
            this.setState({});
               
          }
        })
      }
    }else{ // show chart
    }
 }

When the nodeType is other than button, it means clicked on the stock row, and if it's a button, then we check by textContent and perform required actions. This same function is available up to the StockActionBtn component.

Visualization of action buttons on hovering over a stock row is handled by CSS as below:

.wlshb{
  position: absolute;
  display: none;
  left: 50%;
  translate: -50%;
  top: calc(100% - 5px);
  z-index: 1;
}
.wlshb div{
  margin-top: 10px;
  background: white;
  padding: 1px 10px;
  border-radius: 5px;
  box-shadow: 0 0 5px 5px #0000002e;
  display: flex;
  align-items: center;
  gap: 7px;
}
.item:hover .wlshb{
  display: block;
}
.bsBtn{
  height: 25px;
  width: 25px;
  font-size: 15px;
  pointer-events: none;
  color: white;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.wlshb .b{
  background: green;
}
.wlshb .s{
  background: red;
}

Rest of the CSS for designing our WatchList component is as below:

.stocksC{
  box-sizing: border-box;
  box-shadow:0 0 2px 1px rgba(0, 0, 0, 0.1);
  font-family: math;
}
.stocks{
  height: calc(100% - 40px);
  margin-top: 40px;
  padding-bottom: 40px;
  box-sizing: border-box;
  overflow-y: auto;
  background: white;
}
.item{
  cursor: default;
  padding: 3px 10px;
  border-bottom: 1px solid gainsboro;
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
}
.item:hover{
  background: #e3e3e361; 
}

Now time to celebrate — we have successfully created it with efficient rendering, proper user actions handling, and optimal performance.

I hope we enjoyed the overall coding and building of this component and learned different meaningful things.

If there’s anything you'd like to discuss or explore further, let’s continue the conversation in the comments section—we're all learning and growing together.

Thanks for reading and creating with me. Happy Coding!

Published on May 29, 2025
Comments (undefined)

Read More