B_working_K_hard
B_working_K_hard

Reputation: 91

How to re-render every time state changes with API call limit?

Edit: People from FUTURE: check out this video -

https://www.youtube.com/watch?v=FyxFSkh2wug and the article provided by Leonardo.

I have a searchbar component and a stockchart component that are both children of the Parent component. I want to re-render the stockchart every time a key is entered in the search bar. I am new to react so please bear with my code.

I tried a shouldComponentUpdate and it made too many API calls. In the CorrectRender function, I tried to return the StockChart everytime ticker changed but it didn't work.

I've tried my best to describe the methods I wrote, please feel free to ask any questions.

This is the Parent component, I'm calling this inside App.js

import React, { Component } from 'react'
import StockChart from './StockChart';
import SearchBar from './SearchBar';


// Renders stockChart when value is entered
// I tried to re-render by using a current and previousTicker symbols but it didn't work.

function CorrectRender(props, oldtick) {
    const ticker = props.props;
    console.log("props", props)
    const previousTicker = props.oldtick;
    if (ticker.length > 0 && previousTicker !== ticker) {
        return <div>
            <p>{ticker}</p>
            <StockChart stockSymbol = {ticker} previousTicker={previousTicker}/>
        </div>
    }
    return null;
    }

export class Parent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            stockSymbol: '',
            previousTicker: ''
        }
        this.callbackFunction = this.callbackFunction.bind(this);
    }


    shaveData(value) {
        let value1 = value.split(`,`);
        value = value1[0];
        this.setState({
            stockSymbol: value,
        });
    }



    callbackFunction(sS)  {
        this.setState({ previousTicker: this.state.stockSymbol});
        this.shaveData(sS);
    };

    render() {
        return (
            <div>
                <SearchBar parentCallback = {this.callbackFunction}/>
                <CorrectRender props={this.state.stockSymbol} oldtick={this.state.previousTicker}/>
            </div>
        )
    }
}

export default Parent;

This is the SEARCHBAR component.

import React, { Component } from 'react';
import './SearchBar.css';



export class SearchBar extends Component {
    constructor(props) {
        super(props);
        this.state = {
            search: ' ',
            tickerList: [],
        }
    }



    // Get a list of tickers from an API, this is not the api call that is being overused
    // I want the data to show to the user as "Ticker, Company name" which is what the second (.then) is doing
    fetchTickers(stockSymbol) {
        const pointer = this;
        let arr = [];
        console.log(stockSymbol);
        let url = `https://ticker-2e1ica8b9.now.sh/keyword/${stockSymbol}`;

        fetch(url) 
            .then( function(response) {
                return response.json();
            })
            .then ( function(data) {
                for (var key in data) {
                    arr.push(data[key]['symbol'] + ", "+ data[key]['name'])
                };

                pointer.setState({
                    tickerList: arr
                })
            }

            )
    }

    // this is for handling keys in the search bar and rendering new results based on the 
    critera
    textChange = (e) => {
        const value = e.target.value;
        let arr = [];
        if (value.length > 0) {
            const regex = new RegExp(`^${value}`, 'i');
            this.fetchTickers(value);
            arr = this.state.tickerList.sort().filter(tick => regex.test(tick));
        }
        this.setState({
            tickerList: arr,
            search: value
        });
    }


    // This will be called when an option is chosen by user
    tickerChosen(value) {
        let value1 = value.split(`,`);
        value = value1[0];
        this.setState({
            search: value,
            tickerList: []
        });



    };


    // list of suggestions on user side based on criteria
    tickerSuggestions () {
        const { tickerList } = this.state;
        if (tickerList.length === 0) {
            return null;
        }
        return (
            <ul>
                {tickerList.map((ticker) => <li key={ticker} onClick={() =>  {this.props.parentCallback(ticker); this.tickerChosen(ticker);} }>{ticker}</li>)}
            </ul>
        )
    };



    render() {
        const { search } = this.state;
        return (
            <div className= 'SearchBar'>
                <input value = { search } onChange={this.textChange} type='text' />
                {this.tickerSuggestions()}

            </div>
        )
    }
}

export default SearchBar

This is the StockChart component.

import React, { Component } from 'react';
import Plot from 'react-plotly.js';


class StockChart extends Component {
    constructor(props) {
        super(props);
        this.state = {
            stockXValues: [],
            stockYValues: [],
            stockSymbol: '',

        }
}

componentDidMount() {
    this.fetchStock();
}

fetchStock() {
    const pointer = this;
    const symbol = this.props.stockSymbol;

    const API_KEY = `X`;
    let API_Call = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=${symbol}&outputsize=compact&apikey=${API_KEY}`;

    let StockXValuesFunc = [];
    let StockYValuesFunc = [];



    fetch(API_Call)
        .then(
            function(response) {
                return response.json();
            }
        )
        .then(
            function(data) {
                console.log(data);

                for (var key in data['Time Series (Daily)']) {
                    StockXValuesFunc.push(key);
                    StockYValuesFunc.push(data['Time Series (Daily)']
                    [key]['4. close']);
                }
                pointer.setState({
                    stockXValues: StockXValuesFunc,
                    stockYValues: StockYValuesFunc,
                });


            }
        )

}
render() {
  return (
    <div>
        <h2>{this.props.stockSymbol}</h2>
        <Plot
            data={[
            {
                x: this.state.stockXValues,
                y: this.state.stockYValues,
                type: 'scatter',
                mode: 'lines+markers',
                marker: {color: 'red'},
            },
            ]}
            layout={ {width: 720, height: 440, title: 'Closing Price over the last 100 days'} }
        />
    </div>
  )
 }
}

Export default StockChart;

Upvotes: 1

Views: 464

Answers (2)

DevLoverUmar
DevLoverUmar

Reputation: 13933

What you are looking for is Throttling or Debouncing

Throttling executes a given function after a specified amount of time has elapsed.

Debouncing enforces that a function will not be called again until a certain amount of time has passed since its last call. In debouncing, it ignores all calls to a function and waits until the function has stopped being called for a specified amount of time.

To apply throttling or debouncing you can use npm packages underscore,lodash or rxjs.

Throttling Example

    // ...
import { throttle } from lodash;
class autocomp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            results: []
        }
       this.handleInputThrottled = throttle(this.handleInput, 100)
    }
    handleInput = evt => {
        const value = evt.target.value
        const filteredRes = data.filter((item)=> {
            // algorithm to search through the `data` array
        })
        this.setState({ results: filteredRes })
    }
    render() {
        let { results } = this.state;
        return (
            <div className='autocomp_wrapper'>
                <input placeholder="Enter your search.." onChange={this.handleInputThrottled} />
                <div>
                    {results.map(result=>{result})}
                </div>
            </div>
        );
    }
}

Debouncing Example

    // ...
import { debounce } from 'lodash';
class autocomp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            results: []
        }
       this.handleInputThrottled = debounce(this.handleInput, 100)
    }
    handleInput = evt => {
        const value = evt.target.value
        const filteredRes = data.filter((item)=> {
            // algorithm to search through the `data` array
        })
        this.setState({ results: filteredRes })
    }
    render() {
        let { results } = this.state;
        return (
            <div className='autocomp_wrapper'>
                <input placeholder="Enter your search.." onChange={this.handleInputThrottled} />
                <div>
                    {results.map(result=>{result})}
                </div>
            </div>
        );
    }
}

Upvotes: 0

Leonardo Aretakis
Leonardo Aretakis

Reputation: 11

Here you can find a good explanation on how to throttle and avoid doing so many API calls: Improve your react app performance by using throttling and debouncing

Upvotes: 1

Related Questions