I am super new to react, and I have been struggling to figure out what is causing this error in the chrome console
bundle.js:15316 Uncaught (in promise) TypeError: _this2.setState is not a function
I am trying to do a simple login with facebook to a webapp, to learn the login flows.
I have setup my login on / (also my home page route). I don't think the problem is anywhere around routing or anything. This seems to be a problem with binding in react and being new to this framework - I am having a hard time trying to figure out how to solve this.
My / or home route jsx looks like this
import React, { Component } from "react";
import { browserHistory } from 'react-router';
import FacebookLogin from 'react-facebook-login';
export default class Home extends Component {
constructor() {
super();
this.state = { isAuthenticated: false, user: null, token: ''};
this.setInputState = this.setInputState.bind(this);
}
/*logout = () => {
this.setState({isAuthenticated: false, token: '', user: null})
};*/
responseFacebook(response) {
console.log(response)
const accessTokenBlob = new Blob([JSON.stringify({input_token: response.accessToken}, null, 2)], {type : 'application/json'});
const options = {
method: 'POST',
body: accessTokenBlob,
//mode: 'cors',
cache: 'default'
};
fetch('http://localhost:8880/auth/facebook', options)
.then((r) => r.json())
.then(r => {
console.log(r)
if (r.status) {
this.setState({isAuthenticated: true, user: response.id, token: response.accessToken})
}
});
}
componentDidMount() {
browserHistory.push('/');
}
render() {
console.log(this.state)
let content = this.state.isAuthenticated ?
(
<div>
<p>Authenticated</p>
<div>
{this.state.user.name}
</div>
<div>
<button onClick={this.logout} className="button">
Log out
</button>
</div>
</div>
) : (
<div>
<FacebookLogin
appId="2128489194096154"
autoLoad={true}
fields="name,id,picture"
scope="public_profile"
callback={this.responseFacebook} />
</div>
);
return (
<div className="App">
{content}
</div>
);
}
}
The problem seems to be happening on the line containing this section of the code this.setState({isAuthenticated: true, user: response.id, token: response.accessToken})
When I setup my debug on console on the browser, I am seeing this as the replaced content from the this2 error stack link:
fetch('http://localhost:8880/auth/facebook', options).then(function (r) {
return r.json();
}).then(function (r) {
console.log(r);
if (r.status) {
_this2.setState({ isAuthenticated: true, user: response.id, token: response.accessToken });
}
});
I have been at this for almost a day now, and I am completely lost - have been reading a few articles - and have not gotten anywhere. As I keep trying to work through this, if the question is not clear - pls do let me know what more details i can add.
EDIT #1
http://localhost:8880/auth/facebook this is a backend which I have written, and this is something I control. The response log from the backend and the data received at frontend is the same. This tells me that there is no issues with cors or other integration issues.
responseFacebook function is not bound to class context. So this inside responseFacebook function does not refer to the class. You can either use arrow function like this
responseFacebook = (response) => {
Or you can explicitly bind the function in constructor like this
constructor() {
super();
this.state = { isAuthenticated: false, user: null, token: ''};
this.setInputState = this.setInputState.bind(this);
this.responseFacebook = this.responseFacebook.bind(this);
}
Related
I wanted to use some kind of encoding for my web app, because when I saw today, that my password is visible to a naked eye in network tab in DevTools, I thought I would get a heart palpitations or some s**t. I used CryptoJS because it was the first thing that popped me up in the browser.
I started rewriting my code and when I saw some bugs I tried to fix 'em, but this one, is the buggiest bug ever. I googled it, and saw nothing that actually concern me. So I came here and I ask you what actually is going on.
Here's some code:
Code that compilator think is wrong
var key = this._keyPriorReset = this._key;
var keyWords = key.words;
var keySize = key.sigBytes / 4;
Code that I think is wrong
import { Component } from 'react'
import CryptoJS from 'crypto-js'
import doesSesVarExist from '../doesSesVarExist'
import { Redirect } from 'react-router-dom'
import axios from 'axios';
import { ReactSession } from 'react-client-session';
require('dotenv').config();
export default class Info extends Component {
constructor() {
super();
this.state = {
theme: 'dark',
profpic: '../profpic/default.png'
}
}
componentDidMount() {
if(!doesSesVarExist('username') || !doesSesVarExist('password')){
window.location.href = 'http://localhost:3000/account/login'
}
if(doesSesVarExist('theme')){
this.setState({
theme: ReactSession.get('theme')
})
}else{
if(doesSesVarExist('username') && doesSesVarExist('password')){
axios.post('/api/get_theme', {
username: CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(document.getElementById('username').value), process.env.ENCRYPTSECRETKEYUSERNAME),
password: CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(document.getElementById('password').value), process.env.ENCRYPTSECRETKEYPASSWORD)
}).then(response => {
ReactSession.set('theme', response.data.theme)
this.setState({
theme: response.data.theme
})
})
}else{
ReactSession.set('theme', 'dark')
this.setState({
theme: ReactSession.get('theme')
})
}
}
axios.post('/api/get_profpic', {
username: CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(ReactSession.get('username')), process.env.ENCRYPTSECRETKEYUSERNAME),
password: CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(ReactSession.get('password')), process.env.ENCRYPTSECRETKEYPASSWORD)
}).then(response => {
this.setState({
profpic: response.data
})
})
}
render() {
if(!document.body.classList.contains(this.state.theme)){
document.body.classList.remove('dark')
document.body.classList.remove('light')
document.body.classList.add(this.state.theme)
}
const onLogoutClick = () => {
ReactSession.set('username', undefined)
ReactSession.set('password', undefined)
window.location.href = 'http://localhost:3000/account/login'
}
return(
<div id="userinfo">
<img src={'../' + this.state.profpic}></img>
<p>{ReactSession.get('username')}</p>
<div class={this.state.theme} onClick={() => onLogoutClick()}>Log out</div>
</div>
)
}
}
I have a react application that I am trying to render some basic JSON to the screen. I will place a small snippet of the JSON object below. I am trying to represent the data in the researchPage. I will be placing the entire component class below. Please let me know if I can provide any further information to clarify.
db.json - This is the JSON file that I am trying to pull data from.
{
"hits": [
{
"_index": "issflightplan",
"_type": "issflightplan",
"_key": "IDP-ISSFLIGHTPLAN-0000000000000447",
"_version": 1,
"_score": null,
"ContentType": {
"__deferred": {
"uri": "https://bi.sp.iss.nasa.gov/Sites/FP/_api/Web/Lists(guid'a9421c5b-186a-4b14-b7b2-4b88ee8fab95')/Items(252)/ContentType"
}
researchPage - This is the component page that I am trying to render the JSON data too. I have looked at the console and do not seem to be getting any errors. The page is showing, and the H1 Record MetaData is also rendering to the screen, however, there is no H3 or Paragraph below it.
import React, { Component } from "react";
import ReactDOM from "react-dom";
import data from "../../data/db.json";
console.log(data);
class ResearchPage extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
error: null,
dataSet: [],
data: [],
}
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(data)
.then((res) => {
debugger;
if (res.ok) {
return res.json();
} else {
throw Error("Error Fetching Data");
}
})
.then((data) => {
console.log(data);
this.setState({ data: data, isLoading: false });
})
.catch((error) => {
console.log((error) => this.setState({ error }));
});
}
render() {
const { error, isLoading, data } = this.state;
const dataItems = data.map((dataSet) => (
<div key={dataSet.id}>
<h3>{dataSet._index}</h3>
<p>{dataSet.uri}</p>
</div>
));
if (error) {
return <p style={{ color: "red" }}>{error.message}</p>;
}
if (!isLoading) {
return <p>Loading Data...</p>;
} else
return (
<div>
<h1>Record MetaData</h1>
{dataItems}
</div>
);
}
}
export default ResearchPage;
Update:
I got it working.
The problem I was facing was when trying to display the data was that the data in the render method wasn't initialized but, if you do the mapping inside the didmount and also it looks like your trying to access the list so you need to specify that.
const hits = data.hits.map((dataSet) => (
<div key={dataSet.id}>
<h3>{dataSet._index}</h3>
<p>{dataSet.uri}</p>
</div>
));
this.setState({ data: hits, isLoading: false });
render() {
const { error, isLoading, data } = this.state;
if (error) {
return <p style={{ color: "red" }}>{error.message}</p>;
}
if (isLoading) {
return <p>Loading Data...</p>;
} else {
return (
<div>
<h1>Record MetaData</h1>
{data}
</div>
);
}
}
Oh also, I put the db.json in the public directory and accessed it as follows because I was getting module not found.
fetch("db.json", {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}})
.then (...)
How to troubleshooting
To feed some example test data to see if it's and IO issue you can convert the json object manually into a javascript object like so,
this.setState({ data: [
{ _index: 'ia', uri: 'ub' },
{ _index: 'ib', uri: 'ub' },
], isLoading: false });
More Explanation
Issues you faced
Component Lifecycle - The ComponentDidMount will fire after or at the same time that render fires. This raised a concurrency issue where the data you were trying to map was not use was not yet available. By doing the map inside the DidMount ensures that it's performed only once the data is ready. In the meantime the {data} in the render will be null and nothing will be displayed. If you wanted to test this you could put a sleep/block inside your didmount and see that the data won't display until the block is complete.
Correctly navigating the json object - Using data.map instead of data.hits.map was targeting the entire json object and not the specific list "hits" that actually needed to be accessed (data.hits). Next the .map function iterates through that list providing 'dataSet' on each iteration which allow the use that value.
Potential - I don't normally get data from a json file but, instead from web API's so, I have little experience but, normally to access the file you need a special library or have to put the file inside your public folder which is available throughout the project. You can test this by doing http://localhost:3000/db.json
if (!isLoading) {
return <p>Loading Data...</p>;
This is backwards. It should be:
if (isLoading) {
return <p>Loading Data...</p>;
I have a fairly simple ASP.NET site with a react front-end. It has a component MetaWeatherForecast that fetches some data from an API endpoint and displays it in a table. That works fine.
After pulling in react-pull-to-refresh into the project and attaching it to the component, the table initially loads and fetches the data on the first load, but then fails as soon as I pull the table to refresh.
Here's a trimmed version of the component in its current form:
MetaWeatherForecast.js
import React, { Component } from 'react';
import authService from './api-authorization/AuthorizeService'
import Moment from 'moment';
import ReactPullToRefresh from 'react-pull-to-refresh'
export class MetaWeatherForecast extends Component {
static displayName = MetaWeatherForecast.name;
constructor(props) {
super(props);
this.state = {
locationForecast: {}, loading: true, success: true, errorMessage: null };
}
componentDidMount() {
this.populateWeatherData();
}
static renderForecastsTable(locationForecast) {
// html markup for the table
}
static renderError(errorMessage) {
// error markup
}
handleRefresh(resolve, reject) {
let success = this.populateWeatherData();
if (success)
resolve();
else
reject();
}
async populateWeatherData() {
this.setState({ locationForecast: {}, loading: true, success: true, errorMessage: null});
const token = await authService.getAccessToken();
const response = await fetch('api/metaweatherforecast/GetFiveDayForecast/44544', {
headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
});
const baseResponse = await response.json();
console.log(baseResponse);
this.setState({ locationForecast: baseResponse.data, loading: false, success: baseResponse.success, errorMessage: baseResponse.errorMessage });
return baseResponse.success;
}
getContent() {
let contents;
if (this.state.loading) {
contents = <p><em>Fetching forecast...</em></p>
} else {
contents = this.state.success
? MetaWeatherForecast.renderForecastsTable(this.state.locationForecast)
: MetaWeatherForecast.renderError(this.state.errorMessage);
}
return contents;
}
render() {
return (
<ReactPullToRefresh
onRefresh={this.handleRefresh}
style={{
textAlign: 'center'
}}>
<div>
<p><em>Pull down to refresh</em></p>
<h1 id="tabelLabel" >Meta Weather forecast</h1>
{this.getContent()}
</div>
</ReactPullToRefresh>
);
}
};
The error being thrown after pulling the table is as follows and is thrown inside the handleRefresh() method:
Uncaught (in promise) TypeError: this.populateWeatherData is not a function
Any ideas or suggestions would be most welcome
In react classes, you have to bind this in the constructor
constructor(props) {
...
this.<method> = this.<method>.bind(this);
}
I like using this library.
In the Items component I set the toke in localstorage. The Details component tries to access this token. It gets null
class Items extends Component {
constructor (props) {
super(props);
}
getT = () => {
axios({
method: 'post',
url: '/oauth2/token',
data,
config
})
.then(res => {
if (res.status === 200) {
console.log(res.data)
this.setState({
token: res.data
})
localStorage.setItem('token', JSON.stringify(res.data['access_token']))
} else {
const error = new Error(res.error);
throw error;
}
}).catch(err => {
console.error(err);
alert('Error logging in please try again');
});
}
render () {
return (
<div>
<ul className="instancesUl">
{ this.props.items.map((item, index) =>
<li
key={item.id}
item={item}
onClick = {this.getT}
>
</li>
)
}
</ul>
<Details
/>
</div>
)
}
}
class Details extends Component {
constructor (props) {
super(props);
this.state = {
}
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');
}
componentDidMount() {
axios({
url: `https://app`,
method: "GET"
})
.then(res => {
})
.catch(error => {
})
}
render () {
return (
<div >
</div>
)
}
}
The problem is that Details constructor is invoked earlier at the time when the localStorage is empty, after that your logic to set the token works but Details constructor does not execute again to get the updated value.
Here is an example how you could tweak your code
In short you should not rely on the state everywhere, init your state in a parent component and setState there, pass it properties to the children components via React props, so when you change the state your props will get updated also and force the component which they belong to render the update and call all needed logic.
You are reading before the write.
Root of Problem:
getT method of class Items is responsible for writing the
localstorage and which has async call, It will take some time to get
data from server so you don't know when it will return.
Details component is rendered inside the Items component so it will
be called immediately when it reaches the render method and as discussed in
above point we are not sure token is written is localstorage or not.
Solution:
You can set some value in state saying token_is_saved
You can render Details component only when token_is_saved is true
Initialize state in Items constructor,
this.state = {
token: null
}
Modify your Items's render method as,
{
this.state.token && <Details />
}
Constructor of Details component works once in the very beginning while instantiating the component class and in that case there would be not token available in the localStorage. After if you click on the button, which later makes the call to the server and after promise is being resolved, you store the token. After refreshing your page, it will become available for you.
Is this the correct way of implementation?
import Vue from 'vue';
import axios from 'axios';
const baseURL = "http://127.0.0.1:8000/api/v1";
const token = localStorage.getItem('token');
export default axios.create({
baseURL,
headers: {
'Content-type' : 'application/json',
'Authorization' : 'Bearer ${token}'
}
});
I have been using Redux for the past two days, i'm getting to understand it more, however I encountered a problem which has stopped my progress.
I have an API which has interchangeable parameters.
e.g. api.example.com/data/{date}/.. and api.example.com/more-data/{regId}/..
My <Picker /> selects a value and that value should be passed to the URL, which calls the API and gives the selected data; in my case regionId.
The problem is changing the params without causing errors or getting CORS problem with the Api call. I also want to be able to set the regionId to have an initialState, so I can begin the request with a parameter in the url.
ReqService.js (just for async api calling)
class ReqService {
async getRequest(url) {
try {
let response = await (await fetch(url));
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error('Error: ', error);
}
}
}
export default new ReqService()
actions.js
import ReqService from '../ReqService';
export const IS_FETCHING = 'IS_FETCHING';
export const DATA_FETCHED = 'DATA_FETCHED';
export const ERROR_FETCHING_DATA = 'ERROR_FETCHING_DATA';
const BASE_URL = 'https://api.example.com/';
const DATE_TODAY = new Date().toISOString();
export const getTheData = (regionId) => {
// The regionId is the param i want to pass to the url
const url = `${BASE_URL}/${DATE_TODAY}/${regionId}`;
const request = ReqService.getRequest(url);
return dispatch => {
dispatch({ type: IS_FETCHING });
request
.then((data ) => {
dispatch({ type: DATA_FETCHED, payload: data });
})
.catch(error => {
dispatch({ type: ERROR_FETCHING_DATA, payload: error });
});
};
};
reducer.js
import { IS_FETCHING, DATA_FETCHED, ERROR_FETCHING_DATA } from '../Actions/actions';
const initialState = {
data: [],
fetching: false,
fetched: false,
error: null
};
export const myReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case IS_FETCHING:
return { ...state, fetching: true };
case DATA_FETCHED:
console.log('The Data Fetched ', action.payload);
return {
...state,
fetched: true,
fetching: false,
data: action.payload.data
};
case ERROR_FETCHING_DATA:
return { ...state, fetching: false, error: action.payload.error };
default:
return state;
}
};
The component where the param changes here:
import React, { Component } from 'react'
import {View, Text, Picker} from 'react-native'
import { connect } from '../../node_modules/react-redux';
import { getTheData } from './Actions/actions';
import { bindActionCreators } from "redux";
class FrontPage extends Component {
constructor(props){
super(props);
this.state = {
regionId:0
};
}
changeRegion = (regId) => {
this.props.getTheData(regId);
}
componentDidMount() {}
render() {
return (
<View>
<Text>Front Page</Text>
<Picker selectedValue={this.props.regionId}
onValueChange={itemValue => this.changeRegion(itemValue)}>
<Picker.Item label="One" value='1' />
<Picker.Item label="Two" value='2' />
</Picker>
</View>
)
}
}
const mapStateToProps = state => {
return {
data: state.data,
fetching: state.fetching,
error: state.error
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getTheData }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(FrontPage);
I dont know if I am doing this correct, I looked at different examples and implemented what seems right. Any help will be great.
From what you are sharing it looks like a good implementation of React and Redux.
If you'd like the Picker component initially have a selected value, then set your state to what it should be. In your case, set the state regionId in your FrontPage component.
this.state = {
regionId: 1 // this will pre-select the first value.
};
"The problem is changing the params without causing errors or getting CORS problem with the Api call."
I'm unsure which problems you have when the params are changed. Can you elaborate or include a screenshot?
As for the CORS error message. Have a look at the article How to fix CORS problems to gain a better understanding of it and what you need to change. When getting this error the problem isn’t in the client application but in the server application. To fix it, you need to enable CORS support at the server level.
You can do this by setting the Access-Control-Allow-Origin header. e.g.
Access-Control-Allow-Origin: *
This will allow any host to access the API, even when they are on a different domain or post.