0

I need to click twice on a button linked to the current page so i can get the data fetched from the api to render. I am using nivo / charts to visualize my data.

The component fetches the company list from the api, and a second fetch loops through every result fetching data for every distinct company.

On first try, the company list were to fetch on the parent component, and a fetch request would take place for every child component thereafter (parent=list of chart components, child=Company Chart), but on the pagination process it did not render properly so I had to uplifted the state to the parent component, the problem this time was that the parent component did not render on first click, I had to double click for example link button so that the parent component would render.

I thought the problem might be occurring since there might had been a missynchronization with the componentDidMount order of actions since I was sure that the first and second data fetching (first being the company get request and second distinct company get request), were executing simultaneously rather than one after the other. So I directed to redux and architectured my application to redux rules. It did not resolve anything and still requires to double click on a link so that the rendering would take place.

Now I feel like I would need to add some await/async rules for the api fetching process but I am not sure whether that would work or not, so I would really appreciate getting a second opinion on how to solve this little problem because it has been bugging me for weeks.

my Reducer:

import { FETCH_COMPANIES } from '../actions/types';

const initialState = {
    next : null,
    prev : null,
    items : [],
    item : [],
}



export default function(state = initialState, action) {
    switch (action.type) {
        case FETCH_COMPANIES:
            return {
                ...state,
                items : action.payload.companies,
                next : action.payload.next,
                prev : action.payload.prev,
                
            }

        default:
            return state;
    }
}

my Store.js:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';


const initialState = {};
const middleware = [thunk];

const store = createStore(
    rootReducer,
    initialState,
    compose(
        applyMiddleware(...middleware)
    )
)


export default store;

my Actions:

import axios from 'axios';
import { FloatType } from 'three';
import { FETCH_COMPANIES } from './types'; 





export const fetchAllData = (url) => dispatch => {
    fetch(url)
    .then(res => res.json())
    .then(
        posts => 
        
        dispatch({
            type : FETCH_COMPANIES,
            payload : FetchCall(posts),
        }) 
    )
    

}

function FetchCall(res) {
    let next;
    let prev;
    try {
        next = res.next;
    }
    catch(err) {
        console.log(err)
    }
    try {
        prev = res.previous;
    }
    catch(err) {
        console.log(err)
    }

    const CompanyArray = Array()

    res.results.map(element => {
        axios.get(`https://API/${element.symbol}/`).then((res) => {
            const DataGroup = handleChartData(res.data)
            CompanyArray.push({
                'name' : element.symbol,
                'data' : DataGroup,
            })
        })
        
    });

    const ALL_DATA = {
        'next' : next,
        'prev' : prev,
        'companies' : CompanyArray,
    }

    return ALL_DATA;

}



function handleChartData(data) {
    DataGroup = Object()
    return DataGroup;
}

And my Component:

import React, { useState}  from 'react';
import { Card, Row, Col, Button } from 'antd';
import Chart from '../components/Chart';
import DetailCompany from './CompanyDetail';
import { connect } from 'react-redux';
import { fetchAllData } from '../actions/chartActions';
import PropTypes from 'prop-types';

class CompanyList extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            charts : this.props.charts
        }
    }
    
    componentWillMount() {

        try {
            this.props.fetchAllData("https://API/company/")
        }
        catch(err) {
            console.log(err)
        }
        
    };

 
    prevPage = () => {
        let toPage = this.props.prev
        this.props.fetchAllData(toPage)
       
    }
    nextPage = () => {
        let toPage = this.props.next
        
        this.props.fetchAllData(toPage)
        
    }

    

    

    
    render() {
        
        const chartItems = this.state.charts.map(chart => (
            <Col style={{margin:'0 0 75px 0'}} span={12} key={chart.name}>
                <h1 style={{lineHeight:'2em', margin:'0 0 0 70px'}}>{chart.name}</h1>
                <div className="chart-block">
                        
                        <Chart symbol={chart.name} 
                                data={chart.data.chartData}
                      >
                               
                        </Chart>       
                </div>
            </Col>
        ));
        return (
            <Card>
                <Row>
                    {chartItems}
                </Row>    
                <Row>
                    <Button disabled={(this.props.prev ? false : true )} onClick={() => {this.prevPage()}}>Previous</Button>
                    <Button onClick={() => {this.nextPage()}}>Next</Button>
                </Row>
            </Card>
        )
    }
}


CompanyList.propTypes = {
    fetchAllData : PropTypes.func.isRequired,
    charts : PropTypes.array.isRequired,
}

const mapStateToStore = state => ({
    prev : state.charts.prev,
    next : state.charts.next,
    charts : state.charts.items,
});

export default connect(mapStateToStore, { fetchAllData })(CompanyList);

I would genuinely appreciate if anyone could help me to get around this problem and understand it to prevent further misdirection or reoccurrence. Thank you.

Nald Dev
  • 557
  • 5
  • 15
  • can you try to put your fetch function into ComponentDidMount or ComponentDidUpdate ? – İlker Apr 09 '21 at 20:49
  • I tried it on ComponentDidMount, it resulted the same as ComponentWillMount. Can I call the fetch function this simply in ComponentDidUpdate? I thought it would eventually break fetching all that information continously. – WaveTechStudio Apr 09 '21 at 21:04

2 Answers2

1

Your fetch thunk is not quite right. In particular, this line:

payload : FetchCall(posts),

FetchCall is asynchronous, but you aren't waiting for it to finish before dispatching. Within the FetchCall you are returning ALL_DATA with an empty CompanyArray before the axios calls finish.

You need to complete all fetch calls before returning or dispatching anything. You can do this with Promise/then, but I find it easier with async/await. Either way you need Promise.all to resolve the entire array. (Also I don't know why you use axios in one place and fetch in the other?).

// helper function to fetch the data for one company
const getCompanyData = async (symbol) => {
  const res = await axios.get(`https://API/${symbol}/`);
  return {
    name: symbol,
    data: res.data,
  }
}

export const fetchAllData = (url) => async (dispatch) => {
  const res = await axios.get(url);
  const posts = res.data;
  const {next, prev, results} = posts;

  const companyArray = await Promise.all(
    results.map( element => getCompanyData(element.symbol) )
  );

  dispatch({
    type : FETCH_COMPANIES,
    payload: {
      next,
      prev,
      companyArray,
    }
  });
}
callmenikk
  • 1,358
  • 2
  • 9
  • 24
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
0

One of the issue that I noticed that the fetchcall is an async request so I think the companyarray would be a blank array in the payload. Are you sure you are getting payload.companies when FETCH_COMPANIES is dispatched?

Arjun Nair
  • 111
  • 4