React JS – To many renderings, how can I improve that code?

I’m new to React JS, and I’m trying to build a small project to learn. So far I’m doing well, but I believe that my app renders too much and it prevents me from getting another fiture I’m interested on adding – a localStorage save of the last stock that was search.

My project is a simple stock searching app, getting the prices of a stock by its name or its ticker.

What am I doing wrong? I get the price as I wish, but it takes too many renderings. If I do just one, the price I get is just a plain 0 instead of the real price. When I go like the code posted below, it shows the correct price, but I believe that I miss something.

I’m relatively new to React so it’s part of the learning I guess 🙂

Another question I had, as I understood react-router is suppose to save the last values entered. I do use react router, is the rendering on this page that is changing to the default value all over again?

PS, when I tried to keep the default state of the currentStock empty, I got some weird values which I assume is a problem with the API itself.

Here is my code:

const Stocks = () => {

  const [currentStock, setCurrentStock] = useState('AAPL');
  const [currentPrice, setCurrentPrice] = useState('Loading...');
  const [stockFromClick, setClick] = useState();

  useEffect( () => {

    if(currentPrice === 0){
      setCurrentPrice('Ready!')
    }
    const fetchData = async() =>{
    const getSymbol = await axios(`https://finnhub.io/api/v1/search?q=${currentStock}&token=${API_KEY}`);
    setCurrentStock(getSymbol.data.result[0].symbol);

    const getPrice = await axios (`https://finnhub.io/api/v1/quote?symbol=${currentStock}&token=${API_KEY}`)
    setCurrentPrice(getPrice.data.c)
    }

  fetchData();
  console.log(currentPrice);
  } , [stockFromClick, currentPrice]);


  const handleClick = () =>{
    setCurrentPrice('Loading... Please allow the system a few seconds to gather all the information');

    setClick(currentStock);

    console.log(currentStock);
  }

  return (
    <div>
      Stocks!<br></br>
      <input type="text" placeholder="Search a Company" value={currentStock} onChange={e => setCurrentStock(e.target.value)} /><br></br>
      <button type="button" onClick={handleClick}> Search</button><br></br>
      {currentPrice}

    </div>
  )
}

export default Stocks;

Answer

When Functional Components Rerender

In React with functional components each time you update a state variable you will cause the component to rerender (assuming that the state is actually different than before).

So with your code you have the following:

  1. A potential rerender triggered by onChange={e => setCurrentStock(e.target.value)}
  2. A potential rerender triggered by setCurrentPrice('Loading...');
  3. A potential rerender triggered by setCurrentStock(getSymbol.data.result[0].symbol);
  4. A potential rerender triggered by setCurrentPrice(getPrice.data.c);
  5. A potential rerender triggered by setCurrentPrice('Ready!');
  6. Your effect is rerunning every time the dependencies change.

Again, these rerenders may not occur if those set‘s don’t actually result in a changed state.

How I Would Write It

If I were to write this component myself I’d probably do something like the following. Note, that many of my changes are likely just stylistic preferences. However, the combining of the stock and symbol values into a single state variable could help you eliminate a rerender:

import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';

const Stocks = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [stock, setStock] = useState({});
  const [query, setQuery] = useState('APPL');

  const fetchData = useCallback(async (q) => {

    // don't do anything if we don't have a query string
    if (!q) return;

    setIsLoading(true);
    const getSymbol = await axios(`https://finnhub.io/api/v1/search?q=${q}&token=${API_KEY}`);
    const symbol = getSymbol.data.result[0].symbol;

    const getPrice = await axios(`https://finnhub.io/api/v1/quote?symbol=${symbol}&token=${API_KEY}`);
    const price = getPrice.data.c;

    setStock({ symbol, price });
    setIsLoading(false);
  }, []);

  // load the default stock data on initial rendering
  useEffect(() => fetchData(query), []);

  return (
    <div>
      Stocks! <br/><br/>
      <label for="query">Search For A Stock:</label>
      <input
        name="query"
        type="text"
        placeholder="Ex. APPL"
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <br></br >
      <button
        type="button"
        onClick={() => fetchData(query)}
      >Search</button>
      <br/><br/>

      {isLoading && <span>Loading... Please allow the system a few seconds to gather all the information</span>}
      {!isLoading &&
        <div>
          <div>Symbol: {stock.symbol}</div>
          <div>Price: {stock.price}</div>
        </div>
      }
    </div>
  );
}

export default Stocks;

React Router

I think your question about the react-router probably needs some more detail. There’s no indications in your code of how you’re trying to leverage react-router state or pass it into this component.

Finnhub API

I think the important thing to note about this API is that the /search endpoint is truly returning search results on a string query. Therefore if you pass an empty string it’s running a query on that as the search term and returning results. Similarly, even entering something like APPL is subject to have an unexpected result as it’s not just searching on the stock symbol.