How to implement Google API with React, Redux and

2019-04-26 21:25发布

问题:

I'm trying to get google calendar events into my React Redux app.
I've tried using googleapis and google-auth-library but webpack is throwing errors because googleapis was built to run server side and bundle.js is referenced from client. So I've read a few forums about these errors and they all point to using Google's js client library instead.

I understand how to implement this in a java or php app (I'm old... 35 ;) but I'm new to React Redux and I'm looking for the best way to implement this.

I'm trying to fetch the events from my calendar in my actions.js. I tried including <script src="https://apis.google.com/js/api.js"></script> in my html header and then using gapi.load() from actions.js. I also tried creating a api.js file and referencing that with require('./api'). I also tried to use the cli commands from the Node.js Quickstart guide to get an access_token and then just use axios to call Google API directly but I'm getting a 403. I'm thinking I'm just not providing the proper headers but that wouldn't be best practice anyway.

My question is basically how do I reference Google's js client library from my actions.js file while adhering to Redux standards?

回答1:

Can you try this library which I used to load external libraries and modules in my React app when I couldn't find a NPM module for it:

https://github.com/ded/script.js/

So your code will be like this:

import $script from 'scriptjs';

$script('https://apis.google.com/js/api.js', function () {
  //Put your google api functions here as callback
});


回答2:

You're on the right track by including the official Google client API in the HTML header. It's less than ideal -- it would be nice if Google provided the (browser) client API as an npm module that you could import. But they don't (that I see), so I think what you're doing is fine.

Then there's the question of "how do I use it in a way that's React/Redux friendly?" Redux is a mechanism for managing the state of your application. The Google API is not part of your application (though what you do with it may inform the state of your application).

It's easy to verify that you have access to the Google API: you can just make a call from the componentDidMount method of one of your components, and do a console log:

class MyComp extends React.Component {
  componentDidMount() {
    // this is taken directly from Google documentation:
    // https://developers.google.com/api-client-library/javascript/start/start-js
    function start() {
      // 2. Initialize the JavaScript client library.
      gapi.client.init({
        'apiKey': 'YOUR_API_KEY',
        // clientId and scope are optional if auth is not required.
        'clientId': 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
        'scope': 'profile',
      }).then(function() {
        // 3. Initialize and make the API request.
        return gapi.client.request({
          'path': 'https://people.googleapis.com/v1/people/me',
        })
      }).then(function(response) {
        console.log(response.result);
      }, function(reason) {
        console.log('Error: ' + reason.result.error.message);
      });
    };
    // 1. Load the JavaScript client library.
    gapi.load('client', start);
  },
}

If you don't see what you expect on the console, somehow gapi isn't getting loaded as you expect. If that happens, you'll have a more specific question you can ask!

If you do get a response, you now know how to call GAPI...but then how to make use of it in a Redux-friendly way?

When you make a GAPI call, you probably want to modify your application's state in some way (otherwise why would you be doing it?) For example, you might invoke the auth flow, and when GAPI returns success, your application state now has loggedIn: true or similar (possibly with lots of other state changes). Where you make the GAPI call is up to you. If you want to do it when the component loads, you should do it in componentDidMount. You also may commonly be making the GAPI call in response to a user action, such as clicking on a button.

So the typical flow would be something like this:

// either in componentDidMount, or a control handler, usually:
someGapiCall()
  .then(result => {
    this.props.onGapiThing(result.whatever)
  })

Where this.props.onGapiThing is a function that dispatches an appropriate action, which modifies your application state.

I hope this overview helps...feel free to follow up with more specific questions.



回答3:

I'm going to answer my own question despite some very good correct answers. @MattYao answered my actual question of how to get a js script available for reference in my actions.js file. @Ethan Brown gave a very detailed answer that showed some excellent flow possibilities. @realseanp changed the scope but a valid answer.

I tried all of the above and they worked.

So I'm not sure what I was doing wrong but I was finally able to access the gapi object from actions.js by just adding <script src="https://apis.google.com/js/api.js"></script> to my index head. I'm using pug so it looks like this:

doctype
html
    head
        title MyTitle
        link(rel='stylesheet' href='/static/css/main.css')
        link(rel='stylesheet' href='/static/css/react-big-calendar.css')
        script(src='https://apis.google.com/js/api.js' type='text/javascript')
    body
        div(id='app')
        script(src='/static/bundle.js' type='text/javascript')

Here is my component file:

import React from 'react'
import BigCalendar from 'react-big-calendar';
import moment from 'moment';
import { connect } from 'react-redux'

import { fetchEvents } from '../actions/actions'

BigCalendar.momentLocalizer(moment);
@connect((store) => {
    return {
        events: store.events.events
    }
})
export default class Calendar extends React.Component 
{
    componentWillMount() 
    {        
        this.props.dispatch(fetchEvents())
    }

    render() 
    {
        return (
            <div>
                <BigCalendar
                    events={this.props.events}
                    startAccessor='startDate'
                    endAccessor='endDate'
                    style={{height: 800}}
                />
            </div>
        )
    }
}

And then I was able to get this working in my actions.js file

export function fetchEvents() 
{
  return (dispatch) =>
  {
    function start() 
    {
      // 2. Initialize the JavaScript client library.
      gapi.client.init({
        'apiKey': API_KEY,
        // clientId and scope are optional if auth is not required.
        'clientId': CLIENT_ID,
        'scope': 'profile',
      }).then(function() {
        // 3. Initialize and make the API request.
        return gapi.client.request({
          'path': 'https://www.googleapis.com/calendar/v3/calendars/MY_EMAIL@gmail.com/events?timeMax=2017-06-03T23:00:00Z&timeMin=2017-04-30T00:00:00Z',
        })
      }).then( (response) => {
        let events = response.result.items
        dispatch({ 
          type: 'FETCH_EVENTS_FULFILLED',
          payload: events
        })
      }, function(reason) {
        console.log(reason);
      });
    };

    // 1. Load the JavaScript client library.
    gapi.load('client', start)
}}

I had to make my calendar public to access it this way. So now I'm going to work on the oauth2 stuff :/



回答4:

I would load all the google stuff in my index file before i loaded my webpack bundle (Option 1) . Then I would use redux sagas to call the google apis. Loading the google code before your webpack bundle will ensure everything is ready to go when you call the api from the saga