I am using the expo-auth-session package to make a request to the Spotify API to get access tokens, then saving to AsyncStorage.
A save function that stores the token in AsyncStorage:
const save = async (token) => {
try{
AsyncStorage.setItem('access_token', token)
}
catch(error){
console.log(error)
}
}
A getItem function that gets the access token value from AsyncStorage, and sets that value to the spotifyAccessToken state
const [spotifyAccessToken, setSpotifyAccessToken] = useState('');
const getItem = async () => {
try{
const token = await AsyncStorage.getItem('access_token')
setSpotifyAccessToken(token);
}
catch(error){
console.log(error)
}
}
Using the useAuthRequest from expo-auth-session to make a request to Spotify API, the request code below works.
const discovery = {
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
tokenEndpoint: "https://accounts.spotify.com/api/token"
};
const [request, response, promptAsync] = useAuthRequest({
// responseType: ResponseType.Token,
responseType: 'code',
clientId: client_id,
//clientSecret: client_secret,
scopes: ['user-read-recently-played'],
usePKCE: false,
redirectUri: REDIRECT_URI
}, discovery)
useEffect(() => {
if (response?.type === 'success'){
//console.log(response.params.code);
axios.request({
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
headers: {
'content-type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${new Buffer.from(`${client_id}:${client_secret}`).toString('base64')}`,
},
data: {
grant_type: 'authorization_code',
code: response.params.code,
redirect_uri: REDIRECT_URI
}
}).then(res => {
save(res.data.access_token);
}).catch(err => {console.log(err)})
}
},
[response]);
A button that triggers the user to login using Spotify account, after authenticating, it redirects back to this component screen, however, I want the text below the button to be displayed from "Loading..." to the spotifyAccessToken immediately after it redirects to the component screen, but it wouldn't. After I re-run my application, the token is displayed, which means it was successfully stored in AsyncStorage, but didn't update the state immediately. How can solve this? Thanks.
const [spotifyAccessToken, setSpotifyAccessToken] = useState(null);
useEffect(()=>{
//clearTokens();
// console.log('storage: ' + getValueForfor('access_token'))
// console.log('state: ' + spotifyAccessToken)
getItem()
}, [spotifyAccessToken])
<Button title='login to spotify' onPress={() => promptAsync()}/>
{spotifyAccessToken != '' ? <Text> {spotifyAccessToken} </Text> : <Text> Loading... </Text>}
This might be happening if you are redirecting to the component with
getItemtoo early: before theAsyncStorageis done saving the token. Due to this, at the initial render of the component(withgetItem),AsyncStorage.getItemmight be getting the old value ofaccess_tokenand not the updated one.To possibly fix this issue, try redirecting to the next component only after
AsyncStorage.setItempromise is resolved completely. Something like this:This is how your save function should look like: it should return a
Promisevalue:And redirect to the next component after the
savereturn promise value is resolved:Answering the question you asked in the comments to this answer:
Using
awaitfor aPromisemakes the function wait till the promise is resolved (here whensetItemis done). You do not need to explicitly return aPromisevalue from theasyncfunction in this case. If you do not useawait, the function will return prematurely (without waiting for thesetItempromise). ThesetItempromise will still resolve concurrently just that your code wouldn't be able to know when it is resolved.By using
awaitforsetItemhere, you just propagate promise resolution to the calling function(here in thethen(res => {...})block).In the
then(res => {})block you can either useawaitto wait for thesaveto complete before executing the next statement. Or usethen/catchand add the next statement to execute aftersaveis done in thethenblock.Edit: As OP mentioned in the comments below, the redirection to the next component is done automatically. Well, in this case, setting the value in
AsyncStorageand immediately getting it in the next component might not work as expected because of the above-mentioned reason.First, you will need to check if the auto-redirection to the next component is really done after the
axiosrequest completes or before it, i.e. as soon asresponse?.type === 'success'. I am unable to understand why you have made theaxiosrequest after you already gotsuccessfrom auth requestIf the redirection is happening before the
axiosrequest call then you might be able to access the token in thesuccesscondition itself:If you confirmed the above and the auto-redirection is really done after the
axiosrequest and NOT afterresponse?.type === 'success'then:You could use react-native-session-storage as volatile storage to set and get the token in the same session and use
AsyncStoragein parallel to it to set and get the token in/from persistent memory.So, the
savefunction will look like this withSessionStorage:And
getItemfunction will look like this:Why both storages?
AsyncStorage -> to persist the token when the user re-opens the app.
SessionStorage -> as an immediate way to R/W the value during the same session (gets cleared when the app closes).
Another solution: Use ContextProvider, if your code structure allows it. Wrap the context over the next component to "listen" to token value state change from anywhere in the children components.