I am fetching a profile object from my API following user authentication. The fetch returns the profile object as expected, however my server logger clearly shows a profile object containing an "id" and "username", but the initial object returned to the client has only the "username". I am only able to access the "id" property of the profile abject after I refresh.
Not sure how to fix this, but ive tried everything I can think of...
Login Form
export default class LoginForm extends Component {
static defaultProps = {
onLoginSuccess: () => { }
}
state = { error: null }
handleSubmitJwtAuth = ev => {
ev.preventDefault()
this.setState({ error: null })
const { username, password } = ev.target
//login request
AuthApiService.postLogin({
username: username.value,
password: password.value,
})
//login response
.then(res => {
//updates context profile with username value after login
this.props.updater({ username: username.value })
username.value = ''
password.value = ''
TokenService.saveAuthToken(res.authToken)
this.props.onLoginSuccess()
})
.catch(res => {
this.setState({ error: res.error })
})
}
Profile API Service
const ProfileApiService = {
getProfile() {
return fetch(`${config.API_ENDPOINT}/profile`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `bearer ${TokenService.getAuthToken()}`
}
})
.then(res => {
return (!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
}
);
}
}
(API) Profile Service
const ProfileService = {
getProfile : (db,id) =>{
return db
.from('v_users')
.select('id','username')
.where({id})
.first();
},
serializeProfile(profile){
return {
id: profile.id,
username: xss(profile.username)
};
}
}
initially, console.log(this.state.profile.id) //undefined
after a refresh, console.log(this.state.profile.id) // 7
the server log shows this object being returned initially
{ id: 7, username: 'qber83' }, however as mentioned above, I am unable to access the "id" property without refreshing the browser.
The problem here could be that your state is not updated properly, since the object returned is right the API services work, so here your context updater or this.props.onLoginSuccess() might contain the issue.
Related
login(loginId, password) {
return axios
.post(API_URL + "login", {
loginId,
password
})
.then(response => {
console.log(response.data);
if (response.data) {
localStorage.setItem("token", JSON.stringify(response.data));
localStorage.setItem("user", this.getUser(loginId));
console.log(localstorage.getItem("user");
}
console.log(response.data);
return response.data;
});
}
getUser(loginId){
return axios
.get(API_URL+"user/search/"+loginId,{
headers: { Authorization: `Bearer ${authHeader()} ` },
});
getCurrentUser() {
return (JSON.parse(localStorage.getItem('user')));
}
}
class ViewMytweetComponent extends Component {
constructor(props) {
super(props)
this.onChangeReply = this.onChangeReply.bind(this);
this.state = {
Tweet: [],
reply: "",
user: AuthService.getCurrentUser()
}
this.deleteTweet = this.deleteTweet.bind(this);
}
componentDidMount() {
const { user } = this.state;
console.log(user);
var userId = user.loginId;
TweetDataService.getMyTweet(userId).then((res) => {
this.setState({ Tweet: res.data });
// console.log(this.state.Tweet);
});
}
}
In the login method I call the getUser method and store its return value to localStorage with the key user. The getCurrentUser method is used to return the stored user-item from the localStorage object.
Requesting the previously stored user in the componentDidMount method however fails. Logging the user object to the console produces:
[object Promise].
Does anyone know how to solve this?
since axios.get returns a promise, the getUser method is also returning a promise too. Which is an object, when you try to save it in localStorage in here:
localStorage.setItem("user", this.getUser(loginId));
JavaScript automaticaly converts it to a string, which becomes: [object Promise].
There are a few ways to solve this, for example:
login(loginId, password) {
return axios
.post(API_URL + "login", {
loginId,
password
})
.then(response => {
console.log(response.data);
if (response.data) {
// store the result instead of the promise itself,
// also stringify the result before javascript creates a meaningless string itself.
this.getUser(loginId).then((user)=>localStorage.setItem("user", JSON.stringify(user))
localStorage.setItem("token", JSON.stringify(response.data));
console.log(response.data);
return response.data;
})
}
Of course nested thens aren't exactly a good practice, so maybe it would be nice to rethink class' overal data fetching logic.
enter image description here
Here i have screen shot of my local storage. how can i fetch access token from there pass as headers in below action page. please provide any solution for this. how we can fetch token from local storage using react redux and display in action page.
import axios from 'axios';
export const upiAction = {
upi,
};
function upi(user) {
return (dispatch) => {
var data = {
upiId: user.upiId,
accountNumber: user.accountNumber,
};
axios
.post('http://localhost:9091/upiidcreation', data,
)
.then((res) => {
console.log("res", (res));
const { data } = res;
alert(JSON.stringify(data.responseDesc));
// window.location.pathname = "./homes";
if (data.responseCode === "00") {
window.location.pathname = "./home"
}
})
.catch(err => {
dispatch(setUserUpiError(err, true));
alert("Please Check With details");
});
};
}
export function setUserUpi(showError) {
return {
type: 'SET_UPI_SUCCESS',
showError: showError,
};
}
export function setUserUpiError(error, showError) {
return {
type: 'SET_UPI_ERROR',
error: error,
showError: showError,
};
}
if you just need to fetch the token and send it as header in the api request you can do this
let storageValue =JSON.parse(localStorage.getItem('currentUser')
storageValue object will have the whole thing that you've stored in localStorage .
axios.post('http://localhost:9091/upiidcreation', data, {
headers: {
token : storageValue?.data?.accessToken
}
})
You can get localStorage Object like this
let localStorageObject = JSON.parse(localStorage.getItem('currentUser'));
Then You can use it that object to get access token like this:
localStorageObject?.data?.accessToken
I have a component GigRegister - one of it's functions is to get all the documents from a collection, and return only the documents created by the currently logged in user:
authListener() {
auth().onAuthStateChanged(user => {
if(user){
this.setState({
userDetails:user
},
() =>
firebase.firestore().collection('gig-listing').onSnapshot(querySnapshot => {
let filteredGigs = querySnapshot.docs.filter(snapshot => {
return snapshot.data().user === this.state.userDetails.uid
})
this.setState({
filterGigs: filteredGigs
})
})
) //end of set state
} else {
this.setState({
userDetails:null
})
console.log('no user signed in')
}
})
}
componentDidMount() {
this.authListener();
}
Another function of this component is to capture data from the user and then post it to firebase, after which it redirects to another component.
handleSubmit(e) {
let user = auth().currentUser.uid;
const gigData = {
name: this.state.name,
venue: this.state.venue,
time: this.state.time,
date: this.state.date,
genre: this.state.genre,
tickets: this.state.tickets,
price: this.state.price,
venueWebsite: this.state.venueWebsite,
bandWebsite: this.state.bandWebsite,
user: user
};
auth()
.currentUser.getIdToken()
.then(function (token) {
axios(
"https://us-central1-gig-fort.cloudfunctions.net/api/createGigListing",
{
method: "POST",
headers: {
"content-type": "application/json",
Authorization: "Bearer " + token,
},
data: gigData,
}
);
})
.then((res) => {
this.props.history.push("/Homepage");
})
.catch((err) => {
console.error(err);
});
}
So here's the issue. Sometimes this component works as it should, and the data submit and redirect work as intended. Occasionally though, I'll hit submit but trigger the message TypeError: Cannot read property 'uid' of null . Interestingly, the post request is still made.
I've been logged in both when it succeeds and fails, and I can only assume that this.state.userDetails.uid evaluating to null means that auth state has expired, or that the component is rendering before userDetails can be assigned a value?
The issue I have is that I can't tell if this is an async problem, or an auth state persistence problem, and I also can't figure why it's a sporadic failure.
This line of code might be causing you trouble:
let user = auth().currentUser.uid;
currentUser will be null if there is no user signed in at the time it was accessed (or it's not known for sure if that is the case). This is covered in the API documentation.
Ideally, you should never use currentUser, and instead rely on the state provided by onAuthStateChanged. I talk about this in detail in this blog post. If you do need to use currentUser, you should check it for null before referencing properties on it.
You should also know that getting an ID token is best done by using a listener as well. The call is onIdTokenChanged, and it works like the auth state listener.
Keep in mind also that setState is asynchronous and doesn't set the state immediately. It's possible that your Firestore query isn't getting the state it needs immediately.
I'm using github's API to get a list of my repos and then filtering them to display some specified projects. The issue I'm having is some of the data is another API endpoint that needs to be requested. I've never encountered this before. Am I suppose to make another fetch request after I get the first dataset back from the API? For instance I'm currently using this specific end point
https://api.github.com/user/repos
and here is some of the data I get back
archive_url: "https://api.github.com/repos/myUserName/test-repo/{archive_format}{/ref}"
archived: false
assignees_url: "https://api.github.com/repos/myUserName/test-repo/assignees{/user}"
blobs_url: "https://api.github.com/repos/myUserName/test-repo/git/blobs{/sha}"
branches_url: "https://api.github.com/repos/myUserName/test-repo/branches{/branch}"
languages_url: "https://api.github.com/repos/alenart91/test-repo/languages"
so now if I want the languages_url data I have to make another fetch request?
here is my current code
import ProjectCard from './ProjectCard.js'
class Projects extends React.Component {
constructor(props) {
super(props);
this.state = { projects: [], name: '' }
}
componentDidMount() {
this.getProjects();
}
getProjects = () => {
const token = 'private data';
let url = 'https://api.github.com/user/repos';
fetch(url , {
mode: 'cors',
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': 'Bearer ' + token
}})
.then(res => {
if (res.status === 200) {
return res.json();
} else {
return res.statusText;
}
})
.then( data => {
let filteredProjects = data.filter( projectName => {
return projectName.name == 'test-repo' || projectName.name == 'test-repo';
})
this.setState( {projects: filteredProjects });
})
.catch( err => {
console.log(err);
})
}
render() {
const {projects} = this.state;
return (
<React.Fragment>
{projects.map( projects => {
return <ProjectCard language = {projects.language} description = {projects.description} url = {projects.html_url} name = {projects.name} />
})}
</React.Fragment>
);
}
}
export default Projects
Yes, you need to make another fetch request for the languages_url endpoint. Alternatively, you can use GraphQL to get the languages of a repository in a single request. See below code for the GraphQL call. Ref. GraphQL
{
repository(owner: "<<OWNER>>", name: "<<REPONAME>>") {
name
languages(first: 10) {
edges {
node {
name
}
}
}
}
}
I am creating a React calendar that take data from "Microsoft Outlook Calendar" using the client-side JavaScript SDK "hello.js" and Microsoft Graph (for the set up I also followed this guide: https://learn.microsoft.com/en-us/graph/auth-register-app-v2).
Using hello.login my app shows the calendar without any problem...but unfortunately I have to show it without a login session.
This is my code:
class CalendarView extends Component {
constructor(props) {
super(props);
hello.init({
microsoft: {
id: APP_ID,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
},
scope_delim: ' ',
form: false,
scope: SCOPES,
},
});
const { startDate, endDate } = this.props;
this.state = {
// events: [],
startDataTime: startDate.toISOString(),
endDataTime: endDate.toISOString(),
token: hello('microsoft').getAuthResponse().access_token,
};
}
In this other component I mange the Microsoft Graph Query:
class EventsList extends Component {
constructor() {
super();
this.state = {
events: [],
};
}
componentWillReceiveProps(nextProps) {
const { startDate, endDate, token } = nextProps;
// to know what is the Bearer toke
// -> https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly
axios.get(
`https://graph.microsoft.com/v1.0/me/calendarview?startdatetime=${startDate}&enddatetime=${endDate}&orderby=start/dateTime`,
{ headers: { Authorization: `Bearer ${token}` } },
).then(response => this.setState({ events: response.data.value }))
.catch((error) => {
console.log(error.response);
});
}
render() {
const { events } = this.state;
if (events !== null) return events.map(event => <EventList key={event.id} event={event} />);
return null;
}
}
The strange thing is that if I make a console.log(token) the app show me the token but, at the same time, I receive an "GET...401 (Unauthorized)" error
console log token and error message
That are my app propriety:
app propriety part 1
app propriety part 2
Maybe the problem is the Hello.js call?
I am testing my app with Jest and I have this error, can it be linked to my problem?
console.error node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/virtual-console.js:29
Error: Uncaught [TypeError: hello is not a function]
How Can I solve?
I found the solution!
I had to make 2 axios call:
one to obtain the token (with a POST)
one for use the token in my microsoft graph query (with a GET)
I had to register my app here https://portal.azure.com/#home so to obtain a Client ID and Secret.
After I needed to send a POST message to Azure Active Directory Authentication endpoint with following body parameters:
grant_type: The flow we want to use, client_credentials in my case.
client_id: The Client ID (Application ID) of the application I
created in the registration step;
client_secret: The Client Secret I created in the registration
step;
resource: The name of the resource I would like to get access,
https://graph.microsoft.com in this case.
So I created one component with the following axios POST request:
componentDidMount() {
axios.post(`https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${AZURE_ACTIVE_DIRECTORY_TENANT_NAME}/oauth2/token`,
`grant_type=${GRANT_TYPE}&client_id=${APP_ID}&client_secret=${SECRET}&resource=${RESOURCE}`).then(res => this.setAccessToken(res.data.access_token))
.catch((error) => {
console.error(error.response);
});
}
setAccessToken(token) {
if (typeof token === 'string') this.setState({ accessToken: token });
}
NOTE
the resource value needed to be a bit changed to work:
https%3A%2F%2Fgraph.microsoft.com%2F
I had to put the string 'https://cors-anywhere.herokuapp.com' before micorsoftonline URL because otherwise the application generated
"a blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
(I don't know why, I am still working on it because putting this string before is not an optimal solution).
In EventList component I didn't need hellojs anymore, so I just use the token I generated to access. I had to change just a bit the microsoft graph query:
componentDidMount() {
const { accessToken } = this.props;
const { startDate, endDate } = this.state;
this.getEvents(startDate, endDate, accessToken);
}
getEvents(startDate, endDate, accessToken) {
const startDateString = startDate.toISOString();
const endDateString = endDate.toISOString();
axios.get(
`https://graph.microsoft.com/v1.0/users/${USER_PUBLIC_ID}/calendarview?startdatetime=${startDateString}&enddatetime=${endDateString}&orderby=start/dateTime`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
).then(response => this.setEvents(response.data.value))
.catch((error) => {
console.error(error.response);
});
}
setEvents(events) {
const validEvent = event => typeof event.subject === 'string';
this.setState({ events: events.filter(validEvent) });
}
I hope that my solution can be usefull also to other users