在React组件中的Async Meteor.call

时间:2016-04-04 20:51:50

标签: javascript meteor reactjs

我有一个React组件,其中包含一个google-maps实例。我使用Meteor作为基础架构,问题是React组件是同步呈现的,但是为了获取服务器端变量,我不得不求助于异步。如何恰当地处理这种情况?这是我的代码段:

import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Gmaps, Marker, InfoWindow, Circle } from 'react-gmaps';

// Goals for this:
// - get the location of the user dynamically
// - see other users as markers

const coords = {
  lat: 51.5258541,
  lng: -0.08040660000006028,
};

export default class App extends React.Component {
  componentWillMount() {
    const params = {
      v: '3.exp',
      key: null,
    };

    Meteor.call('googleMapsApiKey', (err, res) => {
      if (!err) {
        params.key = res;
        console.log('params', params);
      }
    });
  }

  onMapCreated(map) {
    map.setOptions({
      disableDefaultUI: true,
    });
  }

  onDragEnd(e) {
    console.log('onDragEnd', e);
  }

  onCloseClick() {
    console.log('onCloseClick');
  }

  onClick(e) {
    console.log('onClick', e);
  }

  onDragStart(e) {
    console.log('onDragStart', e);
  }

  render() {
    return (
      <Gmaps
        width={'800px'}
        height={'600px'}
        lat={coords.lat}
        lng={coords.lng}
        zoom={12}
        loadingMessage={'Be happy'}
        params={params}
        onMapCreated={this.onMapCreated}
      >
        <Marker
          lat={coords.lat}
          lng={coords.lng}
          draggable
          onDragStart={this.onDragStart}
        />
        <InfoWindow
          lat={coords.lat}
          lng={coords.lng}
          content={'Hello, React :)'}
          onCloseClick={this.onCloseClick}
        />
        <Circle
          lat={coords.lat}
          lng={coords.lng}
          radius={500}
          onClick={this.onClick}
        />
      </Gmaps>
    );
  }
}

这是显示同步/异步问题的告密者控制台错误:

enter image description here

渲染方法似乎不等待异步响应发生。解决这个问题的最佳做法是什么?

3 个答案:

答案 0 :(得分:2)

当您设计React组件时,您应该考虑它可能具有的多个状态。例如,您的Google地图组件必须在显示地图之前加载,因此它有两种状态:加载和加载。这取决于您的Meteor调用产生的密钥:

export default class App extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      v: '3.exp',
      key: null
    };

    Meteor.call('googleMapsApiKey', (err, res) => {
      if (!err) {
        this.setState({ key: res })
      }
    });
  }

  ...

  render() {
    if (this.state.key) {
      return (
        <Gmaps
          width={'800px'}
          height={'600px'}
          lat={coords.lat}
          lng={coords.lng}
          zoom={12}
          loadingMessage={'Be happy'}
          params={this.state}
          onMapCreated={this.onMapCreated}
        >
          <Marker
            lat={coords.lat}
            lng={coords.lng}
            draggable
            onDragStart={this.onDragStart}
          />
          <InfoWindow
            lat={coords.lat}
            lng={coords.lng}
            content={'Hello, React :)'}
            onCloseClick={this.onCloseClick}
          />
          <Circle
            lat={coords.lat}
            lng={coords.lng}
            radius={500}
            onClick={this.onClick}
          />
        </Gmaps>
      );
    } else {
      return <div>Loading...</div>;
    }
  }
}

基本上,我刚刚用状态替换了你的const params加上一些加载管理。请注意,在ES6组件中,您必须在构造函数中声明this.state

答案 1 :(得分:1)

使用React.Component时,主要区别在于componentwillmount逻辑应该放在类构造函数中,如下所示:

来自babel博客:

  

所有生命周期方法都可以按照您在使用新类语法时的预期方式进行定义。班级&#39;构造函数现在假定以前由componentWillMount填充的角色:

// The ES5 way
var EmbedModal = React.createClass({
  componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
  constructor(props) {
    super(props);
    // Operations usually carried out in componentWillMount go here
  }
}

Link to blog post

答案 2 :(得分:0)

异步工作应该放在componentDidMount中。并且有library可以帮助您创建具有异步道具的组件。 这是一个例子。

import { Meteor } from 'meteor/meteor';
import React from 'react';
import PropTypes from 'prop-types';
import { Gmaps, Marker, InfoWindow, Circle } from 'react-gmaps';
import { AsyncComponent } from 'react-async-wrapper';

// Goals for this:
// - get the location of the user dynamically
// - see other users as markers

const coords = {
  lat: 51.5258541,
  lng: -0.08040660000006028,
};

class App extends React.Component {

  onMapCreated(map) {
    map.setOptions({
      disableDefaultUI: true,
    });
  }

  onDragEnd(e) {
    console.log('onDragEnd', e);
  }

  onCloseClick() {
    console.log('onCloseClick');
  }

  onClick(e) {
    console.log('onClick', e);
  }

  onDragStart(e) {
    console.log('onDragStart', e);
  }

  render() {
    return (
      <Gmaps
        width={'800px'}
        height={'600px'}
        lat={coords.lat}
        lng={coords.lng}
        zoom={12}
        loadingMessage={'Be happy'}
        params={this.props.params}
        onMapCreated={this.onMapCreated}
      >
        <Marker
          lat={coords.lat}
          lng={coords.lng}
          draggable
          onDragStart={this.onDragStart}
        />
        <InfoWindow
          lat={coords.lat}
          lng={coords.lng}
          content={'Hello, React :)'}
          onCloseClick={this.onCloseClick}
        />
        <Circle
          lat={coords.lat}
          lng={coords.lng}
          radius={500}
          onClick={this.onClick}
        />
      </Gmaps>
    );
  }
}

App.propTypes = {
    params: PropTypes.object
}

App.defaultProps = {
    params: {
        v: '3.exp',
        key: null
    }
}

const AsyncApp = () => (
    <AsyncComponent asyncProps={{
        params: () => new Promise((resolve, reject) => {
            Meteor.call('googleMapsApiKey', (err, res) => {
                if (!err) {
                    resolve({
                        v: '3.exp',
                        key: res,
                    })
                } else {
                    reject(err)
                }
            });
        })
    }}
    >
        <App />
    </AsyncComponent>
)