Google Calendar API with React - reactjs

I'm trying to use Google Calendar API in a React and Firebase app. I'm authenticating to Google in my App.js component, while I attempt to access the Google Calendar API in my Calendar.js component.
Here's my App.js:
import React from 'react'
import firebase from 'firebase'
import './App.css'
import AppBody from '../AppBody'
import AppHeader from '../AppHeader'
class App extends React.Component {
constructor() {
super()
this.state = {
user: null,
googleAuth: null
}
}
componentDidMount() {
// capture authentication callback
firebase.auth().onAuthStateChanged(user => {
if (user !== this.state.user) {
this.setState({user})
}
})
const script = document.createElement('script')
script.src = 'https://apis.google.com/js/api.js'
script.onload = () => {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId: 'client_Id',
scope: 'https://www.googleapis.com/auth/calendar'
}).then(() =>
this.setState({googleAuth: window.gapi.auth2.getAuthInstance()})
)
})
}
document.head.appendChild(script)
}
handleAuthClick = () => {
let {googleAuth} = this.state
if (this.state.user) {
googleAuth.signOut().then(firebase.auth().signOut())
} else {
googleAuth.signIn().then(googleUser => {
var unsubscribe = firebase.auth().onAuthStateChanged(firebaseUser => {
unsubscribe()
if (!isUserEqual(googleUser, firebaseUser)) {
var credential = firebase.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token)
firebase.auth().signInWithCredential(credential)
.then(user => this.setState({user}))
.catch(error => console.error(error))
} else {
console.log('User already signed-in Firebase.')
console.log(googleUser)
}
})
})
let isUserEqual = (googleUser, firebaseUser) => {
if (firebaseUser) {
var providerData = firebaseUser.providerData
for (let i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID &&
providerData[i].uid === googleUser.getBasicProfile().getId()) {
return true
}
}
}
return false
}
}
}
render() {
let {user} = this.state
return (
<div>
<AppHeader onAuthClick={this.handleAuthClick} user={user} />
{user ? <AppBody userID={user.uid} /> : undefined}
</div>
)
}
}
export default App
And here is my Calendar.js (it's loaded by the AppBody component)
import React from 'react'
import './Calendar.css'
class Calendar extends React.Component {
constructor(props) {
super(props)
this.state = {
events: []
}
}
componentDidMount() {
if (window.gapi && window.gapi.client) {
var timeMin = new Date()
timeMin.setHours(0, 0, 0)
var timeMax = new Date()
timeMax.setHours(23, 59, 59)
window.gapi.client.load('calendar', 'v3', () => {
window.gapi.client.calendar.events.list({
'calendarId': 'primary',
'timeMin': timeMin.toISOString(),
'timeMax': timeMax.toISOString(),
'singleEvents': true
}).then(response => {
let events = []
response.result.items.forEach(event => {
events.push({
title: event.summary,
start: new Date(event.start.dateTime),
end: new Date(event.end.dateTime)
})
})
this.setState({events})
})
})
}
}
render() {
console.log(this.state.events)
return (
...render events...
)
}
}
export default Calendar
The problem is, if I keep refreshing the screen, I get very inconsistent results:
Sometimes I get and render the list of events.
More often than not, I get no events.
On occasion, I get the following error:
Uncaught {result: {…}, body: "{↵ "error": {↵ "errors": [↵ {↵ "domain": "us…e Exceeded. Continued use requires signup."↵ }↵}↵", headers: {…}, status: 403, statusText: null}
I'm obviously not using the Calendar API correctly. What am I doing wrong though?

Assuming that window.gapi is the Calendar API object that you're setting via a script tag and calling off of the window object, please consider whether the script has loaded by the time you're calling it in your componentDidMount.
In the case that the script races ahead of your component mounting, you'll have events rendered.
In the case that the script hasn't loaded when your component is mounting, your state.events array will be empty, resulting in no events.
The last error appears to be a quota error; you can check your api settings here: https://console.cloud.google.com/apis/api/calendar-json.googleapis.com/overview

Related

How to pass state from class components to global state when using react hooks for global state management

For the web app I'm building in React, I need to record audio and be able to somehow put that recorded audio in the app's global state so I can use and manipulate that recorded audio in different components of the app.
My global state is setup using React Hooks (made and managed with useReducer, createContext, useContext) and I believe Hooks only work for functional components, not class components.
So the issue I'm running up against is that every tutorial I've followed to get my browser microphone to work uses class components (like the code below), not functional components. And I'm assuming that this is for good reason because when I've tried to translate these class components into functional components, I get the error: "cannot read property 'finish' of 'undefined'"
Are there ways to take this audio data (blobURL) and pass it to my global state?
Alternatively (and ideally), is there a way to use the microphone to record audio in a functional component instead of a class component?
import MicRecorder from "mic-recorder-to-mp3";
import React from "react";
const Mp3Recorder = new MicRecorder({ bitRate: 128 });
class AudioRecorder extends React.Component {
constructor(props) {
super(props);
window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.state = {
isRecording: false,
isPaused: false,
blobURL: "",
isBlocked: false
};
}
startRecording = () => {
if (this.state.isBlocked) {
console.log("Please give permission for the microphone to record audio.");
} else {
Mp3Recorder.start()
.then(() => {
this.setState({ isRecording: true });
})
.catch(e => console.error(e));
}
};
stopRecording = () => {
this.setState({ isRecording: false });
Mp3Recorder.stop()
.getMp3()
.then(async ([buffer, blob]) => {
const blobURL = URL.createObjectURL(blob)
this.setState({
blobURL: blobURL,
isRecording: false
});
})
.catch(e => console.log(e));
};
checkPermissionForAudio = () => {
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// First get ahold of the legacy getUserMedia, if present
var getUserMedia =
// navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ audio: true })
.then(stream => {
this.setState({ isBlocked: false });
})
.catch(err => {
this.setState({ isBlocked: true });
console.log("Please give permission for the microphone to record audio.");
console.log(err.name + ": " + err.message);
});
};
componentDidMount() {
this.checkPermissionForAudio();
}
render() {
const { isRecording } = this.state;
return (
<React.Fragment>
<button
onClick={this.startRecording}
className="mr-3 add-collec-btn"
disabled={isRecording}
>
Record
</button>
<button
onClick={this.stopRecording}
className="mr-3 delete-btn"
disabled={!isRecording}
>
Stop
</button>
<audio
ref="audioSource"
controls="controls"
src={this.state.blobURL || ""}
/>
</React.Fragment>
);
}
}
export default AudioRecorder;
UPDATE:
This is how I've set up Context in my application and how it's provided through the code. In my store folder, I have three files: Context.js, GlobalStateProvider, and useGlobalState.
Context.js
import { createContext } from 'react';
const Context = createContext({});
export default Context;
GlobalStateProvider.js
This wraps everything in my App.js file
import React from 'react';
import useGlobalState from './useGlobalState';
import Context from './Context';
const GlobalStateProvider = ({ children }) => {
return (
<Context.Provider value={useGlobalState()}>{children}</Context.Provider>
);
}
export default GlobalStateProvider;
useGlobalState.js
import { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'SETISRECORD':
return {
...state,
isRecording: action.payload
}
case 'SETISBLOCKED':
return {
...state,
isBlocked: action.payload
}
case 'setBlobURL':
return {
...state,
blobURL: action.payload
}
default: {
return state;
}
}
};
const useGlobalState = () => {
const [globalState, globalDispatch] = useReducer(reducer, {
isRecording: false,
isBlocked: false,
blobURL: '',
});
return { globalState, globalDispatch };
}
export default useGlobalState;
I then interface with my global state in functional components like so:
const functionalComponent = () => {
const { globalState, globalDispatch } = useContext(Context);
return (
[code]
);
}
Your class-based components can still "consume" the context but the syntax is a little more involved than simply using a useContext React hook.
Context.Consumer
For your case you would import your global state context Context and render the component that needs to access the context via a function child. The child component would then need consume these context values via props.
Some classed-based component:
class MyComponent extends React.Component {
...
render() {
const { myContext: { globalState, globalDispatch } } = this.props;
return (
[code]
);
}
}
Wrap and pass via props:
import MyContext from '../path/to/context';
...
<MyContext.Consumer>
{myContext => <MyComponent myContext={myContext} />}
</MyContext.Consumer>

Authentication in React Native with AsyncStorage

In my project, when uid is saved in AsyncStorage, it means the user has already logged in.
Current code
Index.js
import React from 'react';
import Auth from 'app/src/common/Auth';
import { Text } from 'react-native';
export default class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: true,
};
}
async componentDidMount() {
await this.setState({ auth: await Auth.user() });
}
render() {
const { auth } = this.state;
return (
{!auth ? (
<Text>You need to Log In!</Text>
) : (
<Text>You are Logged In!</Text>
)}
)
}
}
Auth.js
import { AsyncStorage } from 'react-native';
export default {
async user() {
let result = true;
const response = await AsyncStorage.getItem('uid');
if (!response) {
result = false;
}
return result;
},
};
This code is working but I would like to make this more simple like one function.
I would appreciate it if you could give me any advice.
You can use promise and Do all Job in Index.js like
AsyncStorage.getItem('uid').then((uid) => {
this.setState({
auth : (uid) ? true : false
})
})
or simply use
const uid = await AsyncStorage.getItem('uid');
this.setState({ auth : (uid) ? true : false });
Are you going to be using that in more than one spot? why not just do it in your component?
async componentDidMount() {
const auth = await AsyncStorage.getItem('uid');
this.setState({ auth });
}

App not re-rendering on history.push when run with jest

I'm trying to test my LoginForm component using jest and react-testing-library. When the login form is submitted successfully, my handleLoginSuccess function is supposed to set the 'user' item on localStorage and navigate the user back to the home page using history.push(). This works in my browser in the dev environment, but when I render the component using Jest and mock out the API, localStorage gets updated but the navigation to '/' doesn't happen.
I've tried setting localStorage before calling history.push(). I'm not sure what is responsible for re-rendering in this case, and why it works in dev but not test.
Login.test.jsx
import 'babel-polyfill'
import React from 'react'
import {withRouter} from 'react-router'
import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '#testing-library/react'
import Login from '../../pages/Login'
import API from '../../util/api'
jest.mock('../../util/api')
function renderWithRouter(
ui,
{route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},
) {
return {
...render(<Router history={history}>{ui}</Router>),
// adding `history` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
history,
}
}
describe('When a user submits the login button', () => {
test('it allows the user to login', async () => {
const fakeUserResponse = {'status': 200, 'data': { 'user': 'Leo' } }
API.mockImplementation(() => {
return {
post: () => {
return Promise.resolve(fakeUserResponse)
}
}
})
const route = '/arbitrary-route'
const {getByLabelText, getByText, findByText} = renderWithRouter(<Login />, {route})
fireEvent.change(getByLabelText(/email/i), {target: {value: 'email#gmail.com '}})
fireEvent.change(getByLabelText(/password/i), {target: {value: 'Foobar123'}})
fireEvent.click(getByText(/Log in/i))
const logout = await findByText(/Log Out/i)
expect(JSON.parse(window.localStorage.getItem('vector-user'))).toEqual(fakeUserResponse.data.user)
})
})
relevant parts of LoginForm.jsx
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
disableActions: false,
formErrors: null,
};
}
handleLoginSuccess = () => {
const { loginSuccessCallback, redirectOnLogin, history } = { ...this.props };
if (loginSuccessCallback) {
loginSuccessCallback();
} else {
history.push('/');
}
}
loginUser = ({ user }) => {
localStorage.setItem('vector-user', JSON.stringify(user));
}
handleLoginResponse = (response) => {
if (response.status !== 200) {
this.handleResponseErrors(response.errors);
} else {
this.loginUser(response.data);
this.handleLoginSuccess();
}
}
handleLoginSubmit = (event) => {
event.preventDefault();
const {
disableActions, email, password
} = { ...this.state };
if (disableActions === true) {
return false;
}
const validator = new Validator();
if (!validator.validateForm(event.target)) {
this.handleResponseErrors(validator.errors);
return false;
}
this.setState(prevState => ({ ...prevState, disableActions: true }));
new API().post('login', { email, password }).then(this.handleLoginResponse);
return true;
}
}
Login.jsx
import React from 'react';
import { withRouter, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import LoginForm from '../components/LoginForm';
class Login extends React.Component {
constructor({ location }) {
super();
const originalRequest = location.state && location.state.originalRequest;
this.state = {
originalRequest
};
}
render() {
const { originalRequest } = { ...this.state };
return (
<div>
<h1>Login</h1>
<LoginForm redirectOnLogin={originalRequest && originalRequest.pathname} />
<Link to="/forgot">Forgot your password?</Link>
</div>
);
}
}
Login.propTypes = {
location: PropTypes.shape({
state: PropTypes.shape({
originalRequest: PropTypes.shape({
pathname: PropTypes.string
})
})
})
};
export default withRouter(Login);
Currently the await findByText() times out.
I think that's because in your tests you're not rendering any Route components. Without those react-router has no way to know what to render when the route changes. It will always render Login.

Updated page not getting re-rendered using `history.push` in nested routes

I am using nested route. Parent component shows category list and the child component shows modal. On performing delete action in modal i.e. child, I am redirecting to the parent route using history.push. The component is getting rendered but without re-rendering the component i.e. the record which was deleted still appears in the view, but when I refresh the page the record does not appear. Any suggestions on this?
Here is my code -
CategoryList.js
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import CustomisedModal from './../../elements/CustomisedModal';
class CategoryList extends Component {
state = {
categories: [],
}
componentDidMount() {
fetch('http://localhost:8080/xxxx')
.then(results => {
return results.json();
}).then(data => {
this.setState({ categories: data.categories });
})
.catch(error => {
this.setState({ error: true });
});
}
deleteCategoryHandler = (id) => {
**//navigate to modal**
this.props.history.replace('/category/delete/' + id);
}
render() {
if (!this.state.error) {
categories = this.state.categories.map(category => {
return (
xxxxx
)
})
}
return (
<Container>
xxxx
<Route path="/category/delete/:id" exact component={DeleteCategory} /> **<!--Nested route-->**
</Container>
);
}
}
export default CategoryList;
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: this.props.props
}
}
onClose = () => {
this.props.props.history.go('/category');
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default CustomisedModal;
the problem here is your parent component has the fetch call in componentDidMount. use componentWillReceiveProps with some condition to reload the data after delete action. As the current parent state hold the old data.
Hope this will help
Fixed the issue by updating state on receiving response so that component gets re-rendered
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: this.props.props.categories **added categories to state**
}
}
onClose = () => {
this.props.props.history.go('/category');
}
removeByAttr = function (arr, attr, value) {
var i = arr.length;
while (i--) {
if (arr[i]
&& arr[i].hasOwnProperty(attr)
&& (arguments.length > 2 && arr[i][attr] === value)) {
arr.splice(i, 1);
}
}
return arr;
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
let newArray = this.removeByAttr(this.state.categories, 'id', data.id);
this.setState({ categories: newArray }); **//updated setState**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default withRouter(CustomisedModal);**// added withRouter**

React rendering trello card names

I have a react app which has a component as such:
import React, { Component } from 'react';
import '../css/TrelloCards.css';
class TrelloCards extends Component {
componentDidMount() {
const authenticationFailure = () => { console.log('Auth failure') };
const trello = window.Trello;
const getCards = () => {
const error = (error) => {
console.log(error);
}
const cards = (cards) => {
console.log(cards);
}
trello.get('/member/me/cards', cards, error);
}
trello.authorize({
type: 'redirect',
name: 'React Trello',
scope: {
read: 'true',
write: 'true' },
expiration: 'never',
success: getCards,
error: authenticationFailure,
response_type: 'token',
});
}
render() {
return(
<h1>Placeholder</h1>
);
}
}
export default TrelloCards;
I've successfully console logged my cards, but now I want to render them on the page, I've tried
render() {
return(
<ul>
{cards}
</ul>
);
}
I've tried mapping through cards like:
cards.map(card => {
return(
<li>{card.name}</li>
);
}
But I get the error that 'cards' is not defined. I'm pretty new to React and programming in general, any help would be appreciated.
In your case render does not have access to the cards you downloaded through trello (they are only accessible within componentDidMount). One way to get around this is to save the downloaded cards to the react state. render will then be invoked because the state changed and the cards will be rendered.
Example
class TrelloCards extends Component {
constructor() {
this.state = {
cards: [] <-------- define your initial state
}
}
componentDidMount() {
const trello = window.Trello;
trello.authorize({
type: 'redirect',
name: 'React Trello',
scope: {
read: 'true',
write: 'true' },
expiration: 'never',
success: () => console.log('auth success'),
error: () => console.log('auth failure'),
response_type: 'token',
});
trello.get('/member/me/cards',
(cards) => this.setState({ cards }),
^---- set state here (shorthand)
(error) => console.log('could not get cards'))
}
}
render() {
return(
<div>
{this.state.cards.map(card =>
<li>{card.name}</li>)}
^---- render cards from state
</div>
);
}
}

Resources