0
votes

My goal is to use the button on the page to open a websocket connection, subscribe to the ticker feed, then update each list item's price based on the ID of the list item. Currently I have a list that maps through the initial API call's response and saves each object's ID to an array, which in turn is used to build a <li> for each ID. This creates 96 list items. I have also gotten the price to update live via a <p> element in each <li>.

I am having trouble targeting the price for just the matching row ID to the incoming data object's ID so that only that matching row is re-rendered when it gets a match. Below is my code:

ProductRow.js

import React from 'react';

export default function ProductRow(props) {
    return <li key={props.id}><p>{ props.id }</p><p>{props.price}</p></li>;
}

WatchList.js

import React, { useState, useEffect, useRef } from "react";
import { Button } from 'react-bootstrap';
import ProductRow from "./ProductRow";

export default function WatchList() {
  const [currencies, setcurrencies] = useState([]);
  const product_ids = currencies.map((cur) => cur.id);
  const [price, setprice] = useState("0.00");
  const [isToggle, setToggle] = useState();
  const ws = useRef(null);

  let first = useRef(false);
  const url = "https://api.pro.coinbase.com";

  useEffect(() => {
    ws.current = new WebSocket("wss://ws-feed.pro.coinbase.com");

    let pairs = [];

    const apiCall = async () => {
      await fetch(url + "/products")
        .then((res) => res.json())
        .then((data) => (pairs = data));
      
      let filtered = pairs.filter((pair) => {
        if (pair.quote_currency === "USD") {
          return pair;
        }
      });

      filtered = filtered.sort((a, b) => {
        if (a.base_currency < b.base_currency) {
          return -1;
        }
        if (a.base_currency > b.base_currency) {
          return 1;
        }
        return 0;
      });

      
      setcurrencies(filtered);
      first.current = true;
    };
    apiCall();
  }, []);
  

  useEffect(() => {
    ws.current.onmessage = (e) => {
      if (!first.current) {
      
        return;
      }
      let data = JSON.parse(e.data);
      if (data.type !== "ticker") {
        return;
      }
      
      setprice(data.price);
      
      console.log(data.product_id, price);
    };
    
  }, [price]);
  
  const handleToggleClick = (e) => {
    if (!isToggle) {
      let msg = {
        type: "subscribe",
        product_ids: product_ids,
        channels: ["ticker"]
      };
      let jsonMsg = JSON.stringify(msg);
      ws.current.send(jsonMsg);
      setToggle(true);
      console.log('Toggled On');
    }
    else {
      let unsubMsg = {
        type: "unsubscribe",
        product_ids: product_ids,
        channels: ["ticker"]
      };
      let unsub = JSON.stringify(unsubMsg);
  
      ws.current.send(unsub);
      setToggle(false);
      console.log('Toggled Off');
    }
  };
  return (
    <div className="container">
      <Button onClick={handleToggleClick}><p className="mb-0">Toggle</p></Button>
    <ul>
    {currencies.map((cur) => {
      return <ProductRow id={cur.id} price={price}></ProductRow>
    })}
    </ul>
    </div>
  );
}

App.js

import React from "react";
import WatchList from "./components/Watchlist";
import "./scss/App.scss";

export default class App extends React.Component {
  render() {
    return (
          <WatchList></WatchList>
    )
  }
}
1

1 Answers

0
votes
  1. Initialize the price state to be an empty object i.e. {}. We'll refer the price values by the the product_id on getting the response from websocket
// Initialize to empty object
const [price, setprice] = useState({});
...

// Refer and update/add the price by the product_id 
useEffect(() => {
  ws.current.onmessage = (e) => {
    if (!first.current) {
      return;
    }
    let data = JSON.parse(e.data);
    if (data.type !== "ticker") {
      return;
    }
    // setprice(data.price)
    setprice(prev => ({ ...prev, [data.product_id]: data.price}));
    console.log(data.product_id, price);
  };
}, [price]);
  1. Render your ProductRows as
<ul>
   {currencies.map((cur) => {
     return <ProductRow key={cur.id} id={cur.id} price={price[cur.id]}></ProductRow>
   })}
</ul>

You don't have to manage anykind of sorting or searching for the relevant prices for products.