MaterialUI Spinning loader help needed for React/Redux app

Viewed 674
   import { CircularProgress, FormControl, Input, InputLabel } from 
   '@material-ui/core';
    function toKey(s) {
    return s.split("_").map((s, i) => i > 0 ? s.slice(0,1).toUpperCase() + 
  s.slice(1, s.length) : s).join("")
    }

Function to split the returned json object:

  function toLabel(s) {
     return s.split("_").map((s, i) => s.slice(0,1).toUpperCase() + 
     s.slice(1, s.length)).join(" ")
    }

My class:

  class Reports extends Component {
    constructor(props) {
       super(props);

       this.state = {
        report: '',
        filename: 'my-data.csv',
        isLoading: false,
        tableHeaderData: [],
        reports: [
            { name: 'C3 Report', id: 1, actOn: 'c3'},
            { name: 'C4 Report', id: 2, actOn: 'c4'},
            { name: 'C5 Report', id: 3, actOn: 'c5'}
        ],
        categories: {name: 'Cat 1'},
        catSelection: 'Select a Category',
        repSelection: 'Select Report Type',
        isReportSelected: false,
        c4RptFirstInput: '',
        c4RptSecondInput: ''
    }

   }

Not sure about this but went with convention: componentDidMount () { const {dispatch, id} = this.props;

   }

   handleChange (e) {
       // this.setState({ input: e.target.value });
   }

This is the plugin that I'm using to convert the page into a csv file:

     csvHeader () {

    const  data = this.reportData()
    if(data.length === 0) return []
    const keys = Object.keys(data[0])
    return keys.map((k) => {
        const label  = toLabel(k)
        const key = toKey(k)
        return { label, key }
    })

   }


   csvData () {

    const  data = this.reportData()
    if(data.length === 0) return []
    const values = Object.entries(data);

    const keys = Object.keys(data[0])

    const rows = values.map(entries => {
        const record = entries[1];
        return keys.reduce((acc, key, i) => {
            acc[toKey(key)] = record[key]
            return acc
        }, {})

    });
    return rows
   }

Checks if report or package:

   reportData(){
     switch(this.state.report) {
      case 'channels':
        return this.props.channels

    case 'packages':
        return this.props.packages

    default:
        return []
     }
   }

Not sure about this placeholder function but copied it from somewhere:

  placeholder () {
      return (   
          <div>
            <h1 className="display-3">Reports</h1>
            <p className="lead" cursor="pointer" onClick= 
     {this.loadChannelData}>Svc Configuration</p>

        </div>
    );
    }

Was experimenting with this function but wasn't sure how to use it:

    componentWillReceiveProps() {
      }

     handleCategorySwitch = (e) => {
       const name = e.target.name;
        const value = e.target.value;
       this.setState({ [name]: value});

        console.log(`name ${name}, value ${value}`);
       }

This is where the 'subselection' of the second set of drop downs happens:

   handleSubselection = (e) => {
      this.setState({c4RptSecondInput: e.target.value, })
       switch( e.target.value) {
        case 'input3':
        return  this.props.ReportGetAllPackages()
    }

   }

   handleReportSwitch = (e) => {
    const selectedAction = e.target.value;

    if (selectedAction == 'c3') {
        this.setState(prevState => ({
            report: 'channels'
            ,isLoading: true
        }), this.props.ReportGetAllChannels)
    }

    if (selectedAction == 'c4') {
        this.setState(prevState => ({
            report: 'packages'
        }))

    }
    }

Render function:

render () {

    const {filename, reports, catSelection, repSelection, isReportSelected, 
      c4RptFirstInput, c4RptSecondInput} = this.state;

    return (
       <div className="reports">
            {this.placeholder()}

            <div className="flexMode">
                <span className="spanFlexMode">
                    <InputLabel htmlFor="catSelection"></InputLabel>
                    <Select value={catSelection} name={'catSelection'} 
          onChange={(e) => this.handleCategorySwitch(e)}>
                        <MenuItem value="select">Select Category</MenuItem>
                        <MenuItem value={'Cat1'}>Cat 1</MenuItem>
                        <MenuItem value={'Cat2'}>Cat 2 </MenuItem>
                        <MenuItem value={'Cat3'}>Cat 3 </MenuItem>
                    </Select>
                </span>
                <span className="spanFlexMode">
                    <label>Report Name:</label>
                    <Select value={repSelection} name="repSelection" 
           onChange={(e) => this.handleReportSwitch(e)}>
                        <MenuItem defaultValue={'select'}>Select 
              Report</MenuItem>
                        {reports && reports.map((report, index) => <MenuItem 
            key={index} value={report.actOn}>{report.name}</MenuItem>)} 
                    </Select>
                </span>
            </div>

Below are the second set of drop downs that show up conditionally based on selection of a particular field from above select boxes:

            { this.state.report === 'packages' ?  (
                <div>
                <span>
                    <label>Input 1:</label>
                    <Select name="c4RptFirstInput" value={c4RptFirstInput} 
        placeholder={'Select Provider'} onChange={(e) => 
              this.handleSubselection(e)}>
                        <MenuItem value={'Def'}>Select</MenuItem>
                        <MenuItem value={'Provider'}>Provider</MenuItem>
                        <MenuItem value={'Region'}>Region</MenuItem>
                        <MenuItem value={'Zone'}>Zone</MenuItem>
                    </Select>

                </span>
                <span className="spanFlexMode">
                    <label>Input 2:</label>
                    <Select name="c4RptSecondInput" defaultValue= 
           {c4RptSecondInput} value={c4RptSecondInput} onChange={(e) => 
            this.handleSubselection(e)}>
                        <MenuItem value={'Def'}>Select</MenuItem>
                        <MenuItem value={'input2'}>Input 2</MenuItem>
                        <MenuItem value={'input3'}>Input 3</MenuItem>
                        <MenuItem value={'input4'}>Input 4</MenuItem>
                    </Select>

                </span>
            </div>
            ) : null}


            <div>
                <CSVLink data={this.csvData()} headers={this.csvHeader()} 
          filename={filename} target={'_blank'}>
                 <GetAppIcon />
                </CSVLink>

Here is where the spinning loader should do it's thing and disappear once the data is loaded - currently it just keeps on spinning and the data never gets loaded even though I can see that the data has successfully come back from the reducer:

                    {isLoading
                   ? <CircularProgress />
                : (
                    <Table id="t1">
                        <TableHeaders data={this.csvHeader()} />
                        <TableContent data={this.csvData()} />
                    </Table>
                )}

            </div>
        </div>
    )
    }
     }

   const mapDispatchToProps = dispatch => {
     return {
    ReportGetAllChannels: () => dispatch(ReportGetAllChannels()),
    ReportGetAllPackages: () => dispatch(ReportGetAllPackages()),
    }
   }

const defaultState = ({
   state: {},
   channels: [],
   packages: []
    ,isLoading: false
 })
   const mapStateToProps = (state=defaultState) => {
    return ({
    state: state,
    channels: state.RptDetailsReducer.data,
    packages: state.RptPackagesReducer.data
    ,isLoading: false
    })
    }
1 Answers

isLoading variable is not defined in your render method. I see that you defined it in your component's state and inside your reducer. I assume you are referencing one in your state (Since you said it was keep spinning it is probably the case). You set component's isLoading to true in handleSubselection you have this snippet:

if (selectedAction == 'c3') {
        this.setState(prevState => ({
         report: 'channels',
         isLoading: true
     }), this.props.ReportGetAllChannels)
 }

This code will set isLoading to true than dispatch ReportGetAllChannels. However your component's state won't be updated. I don't know what ReportGetAllChannels does but I am guessing it sets its own isLoading to false. Which is different variable.

Also you may want to read this https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow. Once you map your state to props you usually want to pass them directly to child components.

Edit: Quick fix: use this.props.isLoading instead of state, and set isLoading to true inside your dispatched action

Related