Не может вызвать setState с реакцией на последующую выборку данных с анализом папы, чтобы передать новые реквизиты для перерисовки дочернего компонента? - PullRequest
0 голосов
/ 26 февраля 2019

Я прочитал много постов, но это мой первый вопрос!Позвольте мне сначала поблагодарить всех за поддержку и предложить описание проблемы перед длинным блоком кода!Любая помощь приветствуется ... Я думаю, что я близок!

ВОПРОС / ПРОБЛЕМА: мой метод setState работает только при начальной выборке данных.Последующие попытки извлекают новые данные, но не сбрасывают состояние, и, следовательно, дочерний компонент не получает новые реквизиты!

WHY / WHAT: я использую papa-анализ для извлечения данных CSV и их преобразованияв JSON, а затем передать эти данные ребенку.Я использую библиотеку карт D3 под названием реагировать-простые-карты, а затем вызываю анимации с помощью GSAP / Greensock.

При анализе папы по умолчанию используется обратный вызов вместо обещания (хотя обещания возможны), поэтому ясоздал метод _.isMounting, который я могу переключать ... причина в том, что после извлечения асинхронных данных мои анимации больше не запускаются в компоненте child / map, поскольку они вызываются в componentDidMount до завершения выборки!теперь мое исправление работает, но не будет работать снова и снова!Я буду передавать новые данные, чтобы создавать наборы маркеров карты несколько раз и час!Смотрите мой комментарий ниже!

class MapContainerParent extends Component {
  _isMounted = false; //toggling this allows the child component to wait for data fetch to complete before render
  _isRefreshed = false; //tried, but not working?
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      isLoading: true,
      error: null
    };
    this.updateData = this.updateData.bind(this);
  }

  //I'm using papaparse to fetch and convert the csv file to JSON
//it uses a callback instead of a promise, which may be the issue?

  fetchAndConvert() {
    //Papa Parse to convert CSV to JSON...also fetches from S3 bucket
    const Papa = require("papaparse");
    const dataFilePath =
      "url of where the csv file lives";
    Papa.parse(dataFilePath, {
      download: true,
      header: false,
      skipEmptyLines: true,
      complete: this.updateData //callback, which sets the state after the data is in proper format
    });
  }
  //HEre is the callback
  updateData(result) {
    const data = result.data;
    const mapData = data.map(item => {.....
    //takes the resulting json and formats it

    console.log("MapData restructured:", mapData);
    if (this._isMounted) { //I THINK THE PROBLEM IS HERE!
      this.setState({ //Only sets the state on initial render! 
      //makes sense since I have told it to setState when isMounted is true. 
      //Since I'm fetching new data several times an hour, this won't do! 
      //How can I add another condition to reset state when new data is present?
        data: mapData,
        isLoading: false,
        error: false
      });
    } //tried to add an _isRefreshed condition, not working? 
    else if (this._isMounted && this._isRefreshed === false) {
      this.setState({
        data: mapData,
        isLoading: false,
        error: false
      });
    }
  }
  componentDidMount() {
    this._isMounted = true;
    this.fetchAndConvert();
    this._isRefreshed = true; //dn work
    setInterval(this.fetchAndConvert, 30000);
    //runs the function at an interval I set
    //I see the new data when I preview the network request
    //but it does not trigger a new setState???
  }

  componentWillUnmount() {
    this._isMounted = false;
    this._isRefreshed = false; //not working...
  }

  renderLoading() {
    return <div > loading... < /div>;
  }
  renderError() {
    return <div > Error... < /div>;
  }
  renderMap() {
    return ( 
        <div>
           <MapChild data = {this.state.data/> <
         </div>
    );
  }
  render() {
    if (this.state.loading) {
      return this.renderLoading();
    } else if (this.state.data.length > 0) {
      return this.renderMap();
    } else {
      return this.renderError();
    }
  }
}
export default MapContainerParent;

Ответы [ 2 ]

0 голосов
/ 07 марта 2019

Итак ... есть проблема с использованием _.isMounting для вызова setState.Согласно этому сообщению в блоге от React, это анти-паттерн:

https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

Хотя я использовал этот метод на следующем посте в блоге, с некоторыми очень хорошими постоянными диалогами по этому поводуПроблема в комментариях, я до сих пор не нашел то, что я считаю долгосрочным решением.https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component/

На данный момент работает внутри дочернего компонента ... вызовите анимацию в componentDidMount и используйте componentDidUpdate для глубокого сравнения данных / реквизитов (я использовал lodash _.isEqual),и если он получает новые данные, убейте предыдущую анимацию и загрузите ее снова.Пока это выполняется, 2-секундная анимация загрузки занимает некоторое время для поступления данных. Я не хочу делиться этим кодом, потому что я бы также назвал его «анти-паттерном» или просто плохой практикой ... но это работает100% времени!Когда я найду лучший способ, я обновлю тему!

0 голосов
/ 26 февраля 2019

this не будет тем, что вы ожидаете при вызове функции интервала.

Попробуйте связать fetchAndConvert с this в конструкторе, как вы это делаете с updateData, и все будет работать как ожидалось.

constructor(props) {
  super(props);
  this.state = {
    data: [],
    isLoading: true,
    error: null
  };
  this.updateData = this.updateData.bind(this);
  this.fetchAndConvert = this.fetchAndConvert.bind(this);
}
...