Issue with API Module - javascript

I'm working on a project that works with the Spotify API to allow users to search for and add songs/playlists to their account. I'm coding it all in react, and I get to the point where there's no compiler errors, the only error being:
Warning: Each child in an array or iterator should have a unique "key" prop.
Yet nothing on the application seems to work, you can't add songs to playlist, search the playlist, etc. I'm trying to figure out if something is wrong with my API module, because that's the only thing I can think of that has an issue and doesn't throw any errors. The module follows below, or you can view the repo And here is the Spotify API Authorization Document That we were told to use
function Spotify() {
function getAccessToken() {
if(accessToken !== '') {
return accessToken;
} else if (window.location.href.match(/access_token=([^&]*)/) != null){
accessToken = window.location.href.match(/access_token=([^&]*)/);
expiresIn = window.location.href.match(/expires_in=([^&]*)/);
window.setTimeout(() => accessToken = '', expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
} else {
window.location = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectURI}`;
}
async function search(term) {
accessToken=getAccessToken();
try {
let response = await fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.ok) {
let jsonResponse = await response.json();
let tracks = jsonResponse.map(track => ({
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
uri: track.uri
}));
return tracks;
}
} catch (error) {
console.log(error);
}
}
function savePlaylist(name, trackURIs) {
if (name === undefined || trackURIs === undefined) {
return;
} else {
let userAccessToken = getAccessToken();
let headers = {Authorization: userAccessToken};
let userId = findUserId(headers);
let playlistID;
fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": 'application/json'
},
body: {
name: name
}
}).then(response => {return response.json()}
).then(playlist => {
playlistID = playlist.id;
});
}
}
function findUserId(headers) {
accessToken = getAccessToken();
let id;
fetch(`https://api.spotify.com/v1/me`, {headers: headers}
).then(response => {return response.json()}
).then(jsonResponse => {
id = jsonResponse[0].id;
});
return id;
}
}
}
export default Spotify;

There are some problems with your code:
The handleTermChange method in SearchBar.js reads the value from the input incorrectly, it should be: this.setState({term: event.target.value});
The search button is only a link, you have to bind the onClick event, something like this: <a onClick={this.search}>SEARCH</a>
Something is not right in Spotify.js, currently the getAccessToken method encapsulates everything else.
If you inspect your playlist with React Dev Tools, you can see, that there is no id property on track in TrackList.js, so it will be always null. This is why you get the warning.

Related

Spotify api not searching?

I'm working on a project in codecademy that lets you make a playlist through spotify, but when I type something in the search nothing pops out.I assume its something with the spotify api.
I was seeing that spotify changed their endpoints but I'm not really sure. Nothing in the console says any error. Any help?
Spotify.js
const clientId = '**';
const redirectUri = 'http://localhost:3000'
let accessToken;
const Spotify = {
getAccessToken() {
if (accessToken){
return accessToken;
}
//check for access token match
const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);
if(accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1];
const expiresIn = Number(expiresInMatch[1]);
//clears params, allowing to grab new access token when it expires
window.setTimeout(() => accessToken ='', expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
search(term){
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {headers: {
Authorization: `Bearer ${accessToken}`
}
}).then(response => {
return response.json();
}).then(jsonResponse => {
if (!jsonResponse.tracks){
return [];
}
return jsonResponse.tracks.items.map(track => ({
id: track.id,
name: track.name,
artist: track.artist[0].name,
album: track.album.name,
uri: track.uri
}))
})
},
savePlayList(name, trackUris){
if(!name || !trackUris.length){
return;
}
const accessToken = Spotify.getAccessToken();
const headers = { Authorization: `Bearer ${accessToken}`};
let userId;
return fetch('https://api.spotify.com/v1/me', {headers: headers}
).then(response => response.json()
).then(jsonResponse => {
userId = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/users/${userId}/playlists`,
{
headers: headers,
method: 'POST',
body: JSON.stringify({name: name})
}).then(response => response.json()
).then(jsonResponse => {
const playListId = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/users/${userId}/playlists/${playListId}/tracks`, {
headers: headers,
method: 'POST',
body: JSON.stringify({uris: trackUris})
})
})
})
}
}
export default Spotify;
I found that spotify api changed the json response. I removed the [0] in artist: track.artist[0].name to artist: track.artists.name and this worked.

How to set two interceptors in axios

I have two functions that using axios post information to different APIs I created with node and express. Both of them have an interceptor as I get a response from by backend with messages, errors, and other information. Yet when I post the to the second url ("/users/login") the first interceptor still fires off (in the addUser instead of the findUser function) even though it is not in the same function. How do I fix this?
async function addUser(user) {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const interceptorResponse = axios.interceptors.response.use(
(response) => {
if (typeof response.data === "object") {
let success = response.data.registerSuccess;
let errors = response.data.errors;
let data = response.data.data;
let message = response.data.message;
setData(() => {
return { ...data, errors, registerSuccess: success, message };
});
}
return response;
}
);
await axios.post("/users/register", user, config);
axios.interceptors.request.eject(interceptorResponse);
} catch (err) {}
}
async function findUser(user) {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
axios.interceptors.response.use((response) => {
console.log(response);
if (typeof response.data === "object") {
let loginSuccess = response.data.data.loginSuccess;
let message = response.data.message;
console.log(response.data);
setData(() => {
return { ...data, loginSuccess, message };
});
}
return response;
});
await axios.post("/users/login", user, config);
} catch (error) {}
}

Why is my playlist ID undefined when my app tries to save a playlist to my Spotify account?

I am working on the Codecademy Jammming project and I have gotten everything to work correctly except that I am unable to save my playlist to my Spotify account. When I try to run the below code, for some reason I get an error and it tells me the playlistId is undefined.
let accessToken;
const Spotify = {
getAccessToken() {
if (accessToken) {
return accessToken;
}
// check for access token match
const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);
if (accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1];
const expiresIn = Number(expiresInMatch[1]);
//This clears the parameters, allowing us to grab a new access token when it expires
window.setTimeout(() => accessToken = '', expiresIn * 1000);
window.history.pushState('Access Token', null, '/');
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
search(term) {
const accessToken = Spotify.getAccessToken();
return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
headers: {Authorization: `Bearer ${accessToken}`}
}).then(response => {
return response.json();
}).then(jsonResponse => {
if (!jsonResponse.tracks) {
return [];
}
return jsonResponse.tracks.items.map(track => ({
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
uri: track.uri
}));
});
},
savePlaylist(name, trackUris) {
if (!name || !trackUris.length) {
return;
}
const accessToken = Spotify.getAccessToken();
const headers = {Authorization: `Bearer ${accessToken}`};
let userId;
return fetch('https://api.spotify.com/v1/me', {headers: headers}
).then(response => response.json()
).then(jsonResponse => {
userId = jsonResponse.id;
return fetch(`https://cors-anywhere.herokuapp.com/http://api.spotify.com/v1/users/${userId}/playlists`,
{
headers: headers,
method: 'POST',
body: JSON.stringify({name: name})
}).then(response => response.json()
).then(jsonResponse => {
console.log(jsonResponse);
const playlistId = jsonResponse.id;
return fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, {
headers: headers,
method: 'POST',
body: JSON.stringify({uris: trackUris})
});
});
});
}
};
I've tried logging the jsonResponse to the console and I don't see an id property (screenshot). Anyone have any idea why this is happening? I compared my code to the code used for their version of the site and it looks to be pretty much identical. Any help would be much appreciated.
Based on your code snippet, it appears you are making a call to the wrong endpoint to create the playlist
return fetch('https://cors-anywhere.herokuapp.com/http://api.spotify.com/v1/users/${userId}/playlists'
Perhaps try the below based on the documentation.
return fetch(https://api.spotify.com/v1/users/${userId}/playlists'

Way to handle refresh token

I am working on login feature and have problem when refresh token.
When token expire making request to refresh token, remove the old token, and save the new token to AsyncStorage.
After login successfully have to function A and B. The function A is using the new token to make its request. the function B say that it need to refresh the token so make request to refresh token ( the request make successfully, token being refresh) but The token that request A is using now invalid - I think it happens due to asynchronous
This is my code that use to refresh token:
axiosInstance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status === CODE_TOKEN_EXPIRED) {
try {
const token = await authenticationService.getRefreshToken();
const response = await authenticationService.refreshToken(token);
await authenticationService.removeToken();
await authenticationService.storeToken(response.data.params.access_token);
await authenticationService.storeRefreshToken(response.data.params.refresh_token);
error.config.headers.Authorization = 'Bearer ' + response.data.params.access_token;
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.params.access_token;
return axiosInstance(error.config);
} catch (err) {
console.log(2, err);
await authenticationService.removeToken();
navigationService.navigate('LoginForm');
}
}
return Promise.reject(error);
}
);
Anyone know how to handle which asynchronous call for refresh token?
First would be for you to check if you are changing token to the correct axios instance. It is necessary to change Authorization header on error.response config as you did, but also for main axios instance (if you have one) like so: axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
If it is multiple parallel requests going on that could possibly need to be postponed after token is refreshed issue and answer gets complex, but check this gist with full refresh logic with axios.
I have implemented the same scenario in fetch API. you can also do this same in axios API. Try this to avoid interceptor concept.
Api.ts
export const api = ({ method, url, body, isProtected = true }) => {
return new Promise((resolve, reject) => {
const payload = {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
};
if (body !== null) {
(payload as any).body = JSON.stringify(body);
}
/**
* "isProtected" is used for API call without authToken
*/
if (isProtected) {
AsyncStorage.getItem(ACCESS_TOKEN).then(accessKey => {
(payload.headers as any).Authorization = `Bearer ${accessKey}`;
fetch(url, payload)
.then((response: any) => {
/*
* 419 status denotes the timeout of authToken
*/
if (response.status == 419) {
// refresh token
AsyncStorage.getItem(REFRESH_TOKEN).then(refreshKey => {
const payloadRef = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + refreshKey
}
};
/*
* This call refresh the authToken using refreshing call to renew the authToken
*/
fetch(URL.baseUrl + "/refresh", payloadRef)
.then((response: any) => response.json())
.then(response => {
/*
* if refresh token expired. redirect to login page
*/
if (response.status !== codes.SUCCESS) {
if (!User.sessionOver) {
User.sessionOver = true;
Alert.alert(
'Alert',
'Session Timeout',
[
{
text: 'Get back to Login',
onPress: () => {
// get to Login page
}
}
],
{ cancelable: false }
);
}
} else if (response.status == codes.SUCCESS) {
/*
* If refresh token got refreshed and set it as authToken and retry the api call.
*/
AsyncStorage.setItem(ACCESS_TOKEN, response.payload.access_key).then(() => {
(payload.headers as any).Authorization = 'Bearer ' + response.payload.access_key;
fetch(url, payload)
.then(response => response.json())
.then(response => {
if (response.status == codes.SUCCESS) {
resolve(response);
}
})
.catch(error => {
reject(error);
});
});
}
});
});
} else {
resolve(response.json());
}
})
.catch(error => {
reject(error);
});
});
} else {
fetch(url, payload)
.then((response: any) => {
response = response.json();
resolve(response);
})
.catch(error => {
reject(error);
});
}
});
};
MovieService.ts
import { api } from '../services/api';
import { URL } from '../config/UrlConfig';
const getMovies = () => {
const method = 'GET';
const url = URL.baseUrl + '/v1/top/movies';
const body = null;
const isProtected = true;
return api({ method, url, body, isProtected });
};
export { getMovies };
Maybe it will helps - https://gist.github.com/ModPhoenix/f1070f1696faeae52edf6ee616d0c1eb
import axios from "axios";
import { settings } from "../settings";
import { authAPI } from ".";
const request = axios.create({
baseURL: settings.apiV1,
});
request.interceptors.request.use(
(config) => {
// Get token and add it to header "Authorization"
const token = authAPI.getAccessToken();
if (token) {
config.headers.Authorization = token;
}
return config;
},
(error) => Promise.reject(error)
);
let loop = 0;
let isRefreshing = false;
let subscribers = [];
function subscribeTokenRefresh(cb) {
subscribers.push(cb);
}
function onRrefreshed(token) {
subscribers.map((cb) => cb(token));
}
request.interceptors.response.use(undefined, (err) => {
const {
config,
response: { status },
} = err;
const originalRequest = config;
if (status === 401 && loop < 1) {
loop++;
if (!isRefreshing) {
isRefreshing = true;
authAPI.refreshToken().then((respaonse) => {
const { data } = respaonse;
isRefreshing = false;
onRrefreshed(data.access_token);
authAPI.setAccessToken(data.access_token);
authAPI.setRefreshToken(data.refresh_token);
subscribers = [];
});
}
return new Promise((resolve) => {
subscribeTokenRefresh((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(axios(originalRequest));
});
});
}
return Promise.reject(err);
});
export default request;

React Context API not updating store

I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
Repeating because Stackoverlow ask me to more describe text
I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
import React, { useState, createContext, useEffect } from 'react';
import {MainUrl, ApiUrl} from '../config';
export const StoreContext = createContext();
export const StoreProvider = props => {
const getToken = () => localStorage.getItem("token");
const initState = () => ({
token: getToken(),
isAuth: false,
userRole: "",
userName: "",
userGroupId: null,
mainUrl: MainUrl,
apiUrl: ApiUrl,
})
const [store, setStore] = useState(initState());
const getUserInfo = async () => {
if (getToken()) {
try {
const apiConfig = {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}get-me`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update Context API
setStore({
...store,
userRole: responseJson.role,
userName: responseJson.name,
userGroupId: responseJson.group_id,
isAuth: true,
})
} else if(response.status === 401) {
await refreshToken();
// Here I want call this function with refreshed token but React gives old token, although I updated token in refreshToken method
getUserInfo();
} else {
throw new Error(`Возникла ошибка во получения информации об пользователе. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
}
const logout = async (routerHistory) => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}logout`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Remove token from localstorage
localStorage.removeItem("token");
// Reset Context API store
setStore(initState());
// Redirect to login page
routerHistory.push("/");
} else if(response.status === 401) {
await refreshToken();
logout(routerHistory);
} else {
throw new Error(`Возникла ошибка во время выхода. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
const refreshToken = async () => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.mainUrl}refresh-token`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update token in local storage
localStorage.setItem("token", responseJson.token);
// Update Context API
setStore({
...store,
userRole: 'some new role',
token: responseJson.token,
})
// Here I expect that userRole and token are changed but if I console.log(store) I get old token and null for userRole
console.log(store);
} else {
throw new Error(`Возникла ошибка во время обновления токена`);
}
} catch (error) {
throw error;
}
}
useEffect(() => {
// Get user info
getUserInfo();
}, [])
return(
<StoreContext.Provider value={[store, setStore, logout, getUserInfo]}>
{props.children}
</StoreContext.Provider>
);
}
Your setStore() function from useState() hook is an async function, hence you don't get the updated value from your console.log() call (just after the setStore()) immediately.
Also, there's no facility of providing a callback as the second argument to setStore() function, where you can log the updated state.
However, you can move your console.log(store) call inside a useEffect() hook, which triggers after every state update and ensures that you'll get the updated state.
useEffect(() => {
console.log(store);
})
So, as far as the title of your question "React Context API not updating store" is concerned, it's actually updating. It's just you are logging it before it is updated.
Hope this helps!

Categories