简体   繁体   中英

React render infinite loop when reading file content before upload

I'm trying to display some file contents in a React/Redux app when the file has been selected for upload using the FileReader API. I am able to display the contents but calling setState inside the FileReader's onload event handler is causing an infinite render to occur.

import _ from 'lodash';
import React, { Component } from 'react';
import { reduxForm, Field, formValueSelector } from 'redux-form';
import Button from 'material-ui/Button';
import * as actions from '../../actions';
import { connect } from 'react-redux';
import {
  Select,
  TextField,
} from 'redux-form-material-ui';

import { renderFileInput } from '../helpers/form_helpers';

class ImportLeads extends Component {
  state = {
    fields: []
  }

  handleFormSubmit({ leadsCSV }) {
    const { listid } = this.props;
    this.props.importLeads(leadsCSV, listid);
  }

  renderMapping() {
    const { CSVFile } = this.props;
    console.log(CSVFile);
    const temp = [];

    if(CSVFile) {
      const r = new FileReader();
      r.readAsText(CSVFile, "UTF-8");
      r.onload = (e) => {
        const content = e.target.result;
        const firstLine = content.split('\n', 1)[0];
        const fieldsArray = firstLine.split(',');
        console.log(fieldsArray);
        _.map(fieldsArray, field => {
          console.log(field);
          temp.push(<div>{field}</div>);
        });
        this.setState({ fields: temp });
      }
      r.onerror = function(e) {
        console.log("Error reading file");
      }
    }

    return (
      <div>
        {this.state.fields}
      </div>
    );
  }

  render() {
    const { handleSubmit } = this.props;

    return (
      <div>
        <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
          <div>

            <Field
              name="leadsCSV"
              component={renderFileInput}
              type="file"
              text="Import CSV"
            />

            <div style={{ marginTop: '10px' }}>
              <Button type="submit" variant="raised">Upload</Button>
            </div>
          </div>
        </form>
        {this.renderMapping()}
      </div>
    );
  }
}

ImportLeads = reduxForm({
  form: 'importLeads',
})(ImportLeads);

const selector = formValueSelector('importLeads');
ImportLeads = connect(
  state => {
    const CSVFile = selector(state, 'leadsCSV');
    return {
      CSVFile
    }
  }
)(ImportLeads);

export default ImportLeads;

I use redux form to handle the file input and get the value of the file by making it available as props using connect.

I read that an infinite render usually occurs when setState is called inside the render method. Is there a better way to approach this and not have it rendering infinitely? Thanks.

Inside of renderMapping() ,you calling setState causing to re-render the component.

In component render , you have {this.renderMapping()} causing to call again renderMapping .

This process getting repeated n time causing infinite rendering.

You should check in your renderMapping if the fieldsArray is already contains field . If so, you should not push it to the array. (Because you are pushing the same field in another div into your array its constantly causing re-render)

if (fieldsArray.includes(field)) {
  return;
}

React has special "lifecycle" methods that are useful for exactly this use case.

If you define a componentWillMount() method, it will get called before the first render. If you have additional resources you need to load you could kick that off there.

Since it looks like you want to re-run those calculations if the props change you will also want to use componentWillReceiveProps(nextProps) to re-run your calculations based on the new props. Note that this.props does not yet reflect the new props, you'll need to get those from the arguments.

Finally, while unnecessary for your case you should familizarize yourself with componentWillUnmount() which will allow you to clean up after yourself (eg remove event listeners, cancel timers, etc.) if you fail to do that you can easily cause memory leaks and worse.

There quite a few more lifecycle methods you could familiarize yourself with in the docs: https://reactjs.org/docs/react-component.html#the-component-lifecycle

Finally a sample to demonstrate how you might go about using those methods to solve a similar problem of "parsing" a newline separated list.

 class Table extends React.Component { constructor() { super(); this.state = { rows: [] }; } componentWillMount() { this.parse(this.props.string); } componentWillReceiveProps(nextProps) { this.parse(nextProps.string); } parse(string) { // Dummy placeholder for asynchronous operation this.setState({ rows: string.split("\\n") }); } render() { return ( <ul> { this.state.rows.map((row) => <li>{row}</li>) } </ul> ); } } class Sample extends React.Component { constructor() { super(); this.state = { string: "hello world\\ngoodbye world" }; } render() { return (<div> <Table string={this.state.string} /> <textarea onChange={(event) => this.setState({ string: event.target.value })} value={this.state.string} /> </div>); } } ReactDOM.render(<Sample /> , document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="root"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM