Fetch API not showing json data - react-native

I am relatively new to react native and trying to build an app and am using the fetch API to try and get a json file from the api. My problem is that it seems to not have any response when i make the calls to the api.
Here is the function that contains the fetch calls
export const fetchData = url => {
return async dispatch => {
dispatch(fetchingRequest());
try {
let response = await fetch(url);
let json = response.json();
console.log(json);
dispatch(fetchingSuccess(json));
} catch (error) {
dispatch(fetchingFailure(error));
}
};
};
The console.log(json) does not come up when I check in the chrome debugger.
if the url is needed for reference, I used https://randomuser.me/api/.
This function is called in one of my other components. I am also using redux and redux-thunk to store the data in the JSON file.
Edited in:
I believe the problem to be the function is not being executed when called.
I import the function and all the redux actions like this
import {
fetchingSuccess,
fetchingRequest,
fetchingFailure,
fetchData
} from "../data/redux/actions/appActions.js";
The fetchData function is then called in a _onPress function that is written like this
_onPress = () => {
let url = "https://randomuser.me/api/?results=10"
fetchData(url);
console.log("should have fetched");
};
in the console the expected output should be the
JSON contents or error // logged when fetchData is called
should have fetched // logged from _onPress
but instead the console outputs
should have fetched // logged from _onPress

The problem is that response.json() returns a promise, so when you do:
let json = response.json();
Without the await then the log after is still a promise, in order for this to work you must add the await in front of the response.json():
let json = await response.json();
The json() method of the Body mixin takes a Response stream and reads
it to completion. It returns a promise that resolves with the result
of parsing the body text as JSON.

Related

react native setState inside an async asyncStorage function

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 getItem too early: before the AsyncStorage is done saving the token. Due to this, at the initial render of the component(with getItem), AsyncStorage.getItem might be getting the old value of access_token and not the updated one.
To possibly fix this issue, try redirecting to the next component only after AsyncStorage.setItem promise is resolved completely. Something like this:
This is how your save function should look like: it should return a Promise value:
const save = async (token) => {
try{
await AsyncStorage.setItem('access_token', token)
}
catch(error){
console.log(error)
}
}
And redirect to the next component after the save return promise value is resolved:
...
).then(async (res) => {
await save(res.data.access_token);
// Redirect here, after save is resolved
})...
Answering the question you asked in the comments to this answer:
it's not working still, you said that the save function should return a promise value, where in the code should I put it
Using await for a Promise makes the function wait till the promise is resolved (here when setItem is done). You do not need to explicitly return a Promise value from the async function in this case. If you do not use await, the function will return prematurely (without waiting for the setItem promise). The setItem promise will still resolve concurrently just that your code wouldn't be able to know when it is resolved.
By using await for setItem here, you just propagate promise resolution to the calling function(here in the then(res => {...}) block).
In the then(res => {}) block you can either use await to wait for the save to complete before executing the next statement. Or use then/catch and add the next statement to execute after save is done in the then block.
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 AsyncStorage and 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 axios request completes or before it, i.e. as soon as response?.type === 'success'. I am unable to understand why you have made the axios request after you already got success from auth request
If the redirection is happening before the axios request call then you might be able to access the token in the success condition itself:
if (response?.type === 'success'){
// Check if the token is available here?
console.debug(`Response = ${JSON.stringify(response)}`);
// If token is available here itself, then why is the axios request required?
// Save the token here itself...
// Use SessionStorage if required, implementation explained below in the answer
...
}
If you confirmed the above and the auto-redirection is really done after the axios request and NOT after response?.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 AsyncStorage in parallel to it to set and get the token in/from persistent memory.
So, the save function will look like this with SessionStorage:
import SessionStorage from 'react-native-session-storage';
...
const save = async (token) => {
try{
// Set token in SessionStorage as well to allow access to the value immediately
SessionStorage.setItem(`access_token`, token);
// Store token to AsyncStorage to persist it when the app closes.
await AsyncStorage.setItem('access_token', token);
}
catch(error){
console.log(error)
}
}
And getItem function will look like this:
import SessionStorage from 'react-native-session-storage';
...
const getItem = async () => {
try{
let token = await AsyncStorage.getItem('access_token');
// If the token is not yet set in Async Storage, fetch it from Session Storage
// If it's set in Async Storage, use that value
if(!token) // If it's null
token = SessionStorage.getItem('access_token');
setSpotifyAccessToken(token);
// Don't forget to clear both SessionStorage and AsyncStorage on logout!
}
catch(error){
console.log(error)
}
}
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.

Sveltekit: Can't call API from +layout.ts. Error: "Failed to parse URL from /api/..."

This is the code in my +layout.ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
const response = await fetch('/api/thumbnails', { method: 'GET' })
if (response.ok) {
return { json: await response.json() }
}
else {
console.log('There were no blog posts to get.')
// TODO: do something!
}
};
I had the exact same code in a +page.ts file (the only difference was LayoutLoad was changed to PageLoad), and it worked there. I was calling my thumbnails api and populating my page. I don't understand why it doesn't work at all in +layout. It just crashes my web app.
On top of this, LayoutLoad has an error in VsCode that reads Module '"./$types"' has no exported member 'LayoutLoad'. I don't understand why this is. Can somebody help me?
I figured it out, the answer was here: https://kit.svelte.dev/docs/load#making-fetch-requests
To get data from an external API or a +server.js handler, you can use the provided fetch function, which behaves identically to the native fetch web API with a few additional features:
Basically, in my code I had to change this line
export const load: LayoutLoad = async () => {
to this
export const load: LayoutLoad = async ({fetch}) => {
Because without that change I was using the native fetch and not sveltekit's provided fetch.
Also, I seemed to have fixed this error: Module '"./$types"' has no exported member 'LayoutLoad' by updating sveltkit to the latest version.

Enzyme integration testing: axios.get call not being executed in redux-saga

I am trying to setup tests for some an action creator that is triggering a redux saga.
My saga retrieves a word from a local flask server (will always return the same word) and then displays that word. This is not my real-life case but I tried to start with something easy...
My action creator and saga work as expected when I trigger them with a button in my react app (the word is retrieved from the server, stored in my redux store and the displayed with a selector in my react component), but I cannot get the test to succeed.
I would like to test only the redux part, not the actual rendered react component (not sure if that is part of my problem or not)
I use Enzyme for tests, my store is created correctly and can dispatch the action. I can also see that my saga is being called with the console logs:
My test code:
import { Store } from 'redux';
import { RootState } from '../root.reducer';
import { storeFactory } from '../../../test/testUtils';
import { getSecretWord } from './secret-word.actions';
describe('getSecretWord action creator', () => {
let store: Store<RootState>;
beforeEach(() => {
store = storeFactory();
});
test('add response word to state', () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});
});
and my saga function:
export function* getSecretWordSaga(action: getSecretWordAction): Generator<ForkEffect | CallEffect | PutEffect, void, unknown>
{
try {
console.log('getSecretWordSaga() saga started');
console.log('before axios query call:');
const response:any = yield call(api.get, '/api/word');
// const response = {data: { word: 'party'}, status:200}
console.log('axios query returned: ');
console.log(response);
yield put(setSecretWord(response.data.word));
console.log('getSecretWordSaga() saga finsshed');
} catch (err) {
console.log('error occured:');
console.log(err);
console.log('getSecretWordSaga() saga finsshed with errors');
}
}
export function* getSecretWordSagaStart(): Generator<
ForkEffect<never>,
void,
unknown
> {
yield takeLatest(SecretWordActionTypes.GET_SECRET_WORD, getSecretWordSaga);
}
The axios api is very basic and it includes two interceptors for logging purposes:
import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:5000',
responseType: 'json',
});
api.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
api.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
I can see in the logs (in "npm test") that I get log for the line "before axios query call:' and one console.log for the request interceptor (everything looks fine there), but no more logs afterwards (neither success nor error)
If I comment out the "yield call.." and hardcode the response (like in the commented out line below), my saga runs through the end and my test succeeds.
Why is the yield Call(api.get, '/api/word') not being executed (and I don't get any error message)?
The code is my opinion correct as it is running fine when executed in react. My flask server is obviously also running and I can see in the flask app than no call to the api are being made by the running tests.
I obviously plan to mock that api call but was also running into some problems there, that's why I first wanted to get the real api call working.
After trying many different ways for adding a timeout, setting the testing function to async and adding a setTimeout in a promise did work.
It's not ideal as I have to set the timeout to a specific value, but I could not figure out a better way to get it working.
test("add response word to state", async () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
await new Promise(res => setTimeout(res, 1000));
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});

Axios response promise object but need promise value

I request it with the get method.
async getPrograms() {
const response = window.axios.get('http://localhost:8000/programs')
console.log(response);
}
The rotating response is as follows.
The part I need here is the data in [[PromiseValue]]. But I don't know how to get here. I'm writing my code with Vue js. How do I get the data in [PromiseValue]]?
Since axios.get returns a promise, use await to wait for the promise to resolve
async getPrograms() {
const response = await window.axios.get('http://localhost:8000/programs')
console.log(response.data);
}

Nuxt Fetch Doesn't Update on First Load

I'm having the following issue and hope someone could help me on it:
Fetch is not working on the first load (nor on reloads). It only works when on the client-side (when I move between routes).
I've read that watchQuery could help but didn't understand why and how to use it.
<script>
export default {
async fetch() {
const userId = await this.$nuxt.context.store.state.auth.authUser.userId
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
It doesn't work even if I import and use firebase/auth directly.
<script>
import * as firebase from 'firebase/app'
import 'firebase/auth'
export default {
async fetch() {
const userId = await firebase.auth().currentUser
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
Does anyone have any tips for it? I'd really appreciate it.
Thanks!
After literally 3 days searching/testing, I finally found out why I was having this issue.
The problem was that I simply put async/await for fetch but didn't put async/await for the actions itself. Therefore, my getter (in computed) was getting the store state before the dispatches have been finished.
Thanks, everyone!
Warning: You don't have access of the component instance through this inside fetch because it is called before initiating the component (server-side).
async fetch({ store }) {
await store.dispatch('case/fetchMyCases')
await store.dispatch('case/fetchMyPendingCases')
}
If you need parameter:
async fetch({ store, params }) {
await store.dispatch('case/fetchMyCases', params.uid)
await store.dispatch('case/fetchMyPendingCases', params.uid)
}
I gave an example of id. The name of the parameter depends on the name of your page.
_id => params.id
_uid => params.uid
_slug => params.slug
...
Yes, You must put async/await on actions.
async automatically returns a promise
If you don't need the value, in this case, don't anything return.
export const Actions = {
async fetchUsers() {
// It will return automatically promise
await this.$axios.get('API')
}
}
// If you need returne value
// First way
export const Actions = {
async fetchUsers() {
// It will return promise and value
return await this.$axios.get('API')
}
}
// Second way
export const Actions = {
async fetchUsers() {
// It will return promise and value
const response = await this.$axios.get('API')
return response;
}
}