React rendering trello card names - reactjs

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>
);
}
}

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>

Why does my context not update state as expected?

The imageList is empty even though I have used this.context.setImageList to update the state to get the data from the API. I am confused why the state just didn't update. I have been spending a lot of time on this and haven't found the root cause. I'd appreciate if you can guide me through it. Thank you!
Create context and attach it in the Provider
import React, { Component } from 'react';
const ImageListContext = React.createContext({
imageList: [],
error: null,
loading: true,
setError: () => {},
clearError: () => {},
setImageList: () => {},
})
export default ImageListContext
export class ImageListProvider extends Component {
state = {
imageList: [],
error: null,
};
setImageList = imageList => {
this.setState({imageList})
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
render() {
const value = {
imageList: this.state.imageList,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setImageList: this.setImageList,
}
return (
<div>
{this.loading ? <div>Loading Images...</div> :
<ImageListContext.Provider value={value}>
{this.props.children}
</ImageListContext.Provider>}
</div>
)
}
}
Use Context to update imageList array with the data from API and get the data out of the array to display it
import React, { Component } from 'react'
import ImageApiService from '../../services/image-api-service'
import { Section } from '../../components/Utils/Utils'
import ImageListItem from '../../components/ImageListItem/ImageListItem'
import './ImageListPage.css'
import ImageListContext from '../../contexts/ImageListContext'
export default class ImageListPage extends Component {
static contextType = ImageListContext;
componentDidMount() {
//this calls the image API to get all images!
ImageApiService.getImages()
.then(resJson => this.context.setImageList(resJson))
.catch(error => console.log(error))
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
renderImages() {
const { imageList=[] } = this.context;
console.log(imageList)
return imageList.map(image =>
<ImageListItem
key={image.id}
image={image}
/>
)
}
render() {
return (
<Section list className='ImageListPage'>
{this.context.error
? <p className='red'>There was an error, try again</p>
: this.renderImages()}
</Section>
)
}
}

React Native Function ReferenceError

New to React Native here... I'm trying to call a function that does a get request inside a component which is in the render() method.
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default class Home extends React.Component {
static navigationOptions = {
title: 'Details',
};
getMoviesFromApiAsync = () => {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
return responseJson.movies;
})
.catch((error) => {
console.error(error);
});
};
render() {
return (
<React.Fragment>
<Text>{getMoviesFromApiAsync()}</Text>
</React.Fragment>
);
}
}
However, I'm getting ReferenceError: Can't find variable getMoviesFromApiAsync(). Why is this?
Error Image
You need to call the method on your class, this is a basic Vanilla Javascript thing.
<Text>{this.getMoviesFromApiAsync()}</Text>
However, your approach here is not good, you should write the component out to store the results from your api request in component state. this way you dont need to make a request every render cycle!
export default class Home extends React.Component {
static navigationOptions = {
title: 'Details',
};
state = {
movies: []
}
componentDidMount() {
this.getMoviesFromApiAsync()
}
getMoviesFromApiAsync = () => {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
movies: [...this.state.movies, ...responseJson.movies]
})
})
.catch((error) => {
console.error(error);
});
};
render() {
const { movies } = this.state
return (
<React.Fragment>
{ movies.map( (movie, i) => <Text>{movie.title}</Text> ) }
</React.Fragment>
);
}
}

Google Calendar API with React

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

Testing react component - TypeError: data.map is not a function

I have a react component that takes an array of objects and maps them into another react component in the view.
I am having a hard luck while testing it as it gives this error:
TypeError: data.map is not a function
Here's the test i wrote. Notice i a passing the data prop which i believe should make it work?
content.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import Content from '../components/Content.jsx';
describe('<Content />', () => {
const data = {
created: '2017-02-21T09:50:21.441815Z',
duration: 1575,
final_script: 'some script',
language: 'en-GB',
rating: 2,
url: 'some url',
};
it('renders without exploding', () => {
mount(<Content data={ data } />);
});
});
and here's there component itself
content.jsx
function Content(props) {
const { data } = props;
return (
<div className='content_container'>
{
data.map((content, index) => <AudioBox data={ content } key={
index } />)
}
</div>
);
}
Content.propTypes = {
data: React.PropTypes.arrayOf(React.PropTypes.object),
};
export default Content;
and here's the component which calls this Content component
home.jsx
class Home extends Component {
constructor(props) {
super(props);
this.state = {
content: null,
};
this.logout = this.logout.bind(this);
}
componentDidMount() {
const token = localStorage.getItem('i2x_token');
const requestURL = API_CONTENT;
const requestObj = {
method: 'GET',
headers: new Headers({
Authorization: `JWT ${token}`,
}),
};
request(requestURL, requestObj).then((reply) => {
if (reply.results.length > 0) {
this.setState({ content: reply.results });
} else {
console.log('no reply from API');
}
});
}
logout() {
localStorage.removeItem('i2x_token');
browserHistory.push('/');
}
render() {
const data = this.state.content;
return (
<div className='container'>
<Header logout={ this.logout } />
{ data !== null &&
<Content data={ this.state.content } />
}
</div>
);
}
}
export default Home;
What's wrong here?
I think data that you are setting is not an array, because of that you are getting the error.
Check this:
let data = {};
data.map(el => console.log(el))
Make sure that data should be an array, you can put the check also, before using map by Array.isArray, like this:
Array.isArray(data) && data.map((content, index) => <AudioBox data={content} key={index} />
Try this:
Define the data in this way:
const data = [
{
created: '2017-02-21T09:50:21.441815Z',
duration: 1575,
final_script: 'some script',
language: 'en-GB',
rating: 2,
url: 'some url',
}
];

Resources