Getting response back from using redux-thunk and AsyncStorage - react-native

I am using react-native, redux-thunk, jwt and AsyncStorage to authenticate and locate user in this project. It seems that the request goes to backend from the axios, but I do not see the response back ('in axios' and '?' are not displayed) in the chrome console and I have no idea what possibly goes wrong in my code.
in my actions/userAuth.js
import { AsyncStorage } from 'react-native';
import axios from 'axios';
export function updateUserLoc(username, lat, lng) {
return function(dispatch) {
AsyncStorage.getItem('token').then(function(token) {
console.log('out');
axios.put(`${ROOT_URL}/:${username}/location`, {location: [lat, lng], token})
.then((response) => {
console.log('in axios');
// console.log('update user location', response);
dispatch({
type: USER_LOC_UPDATE
});
console.log('?');
})
.catch(err => { console.log('user location err', err); });
})
.catch(err => { console.log('Async Storage err', err)});
}
}
has anyone had this kind of problem before or does anybody know what the problem is in this code and how to debug it?
will appreciate any kind of advice or answer.
Thank you.

I don't think AsyncStorage.getItem returns a Promise. According to the docs, getItem takes an optional second param that is your callback function. Alternatively you can use async/await if you have ES7 support.

Related

#react-native-async-storage/async-storage getItem is giving value as false

I am trying to access react-native Async storage in a different file from where it has been set. In the same file where it is being set , i am able to get the value. but in the different file i am getting "false" as the value.
Any help would be greatly be appreciated.
import AsyncStorage from '#react-native-async-storage/async-storage';
const getData = async () => {
console.log("fetching data from asyncstorage")
try {
const value = await AsyncStorage.getItem('userToken'); << retuning false
if(value !== null) {
console.log(value);
token.token=value;
}
} catch(e) {
console.log(e);
}
}
const callfunction=()=>{
getData();
}
This is how i was able to make it work,If somebody got a better solution please do post here,
As operation with AsyncStorage are happening in async manner, meaning the main thread will not wait for the function call to be coompleted, i found it best to call the function way before i needed it and keep it stored.
In order to acheive this i used a react hook "useEffect" this is similar to springboot alternative #PostConstruct, meaning "useEffect" will be called the moment you are navigate to the page. So the code looks something like this.
import AsyncStorage from '#react-native-async-storage/async-storage';
import React,{useEffect} from 'react';
const [token, setToken] = React.useState({
token: "",
});
const getData = () => {
console.log("fetching data from asyncstorage")
AsyncStorage.getItem('userToken').then((value) => {
console.log("token : "+ value);
token.token=value;
}).done();
}
useEffect(() => {
if(token.token=="")
{
getData();
}
});
If someone finds any good blog or video supporting this do post it here, also if my explanation, is deviating from technical aspect please help me understand it better.

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);
});

firebase.auth is not a function when dispatching login action

I'm trying to connect redux and firebase using react-redux-firebase (3.x.x) and react-native-firebase v6 modules. I'm following documentation and examples from react-redux-firebase docs, considering their v3 migration guide, but it seems i just can't get it to work. When i dispatch following login action I get the error:
TypeError: firebase.auth is not a function
Firebase app is natively initialised and when I create store I use HOC ReactReduxFirebaseProvider. Has anyone already had a similar case?
I tried to manually initialize firebase app but i guess this is not the case for my problem as i get warning that default app is already initialized.
Thanks in advance.
export const login = credentials => {
return (dispatch, getState, {getFirebase}) => {
const firebase = getFirebase();
// console.log(firebase); <-- here i control firebase instance and it seems ok
firebase
.login(credentials) <-- error is thrown
.then(() => {
dispatch({type: LOGIN_SUCCESS});
})
.catch(err => {
dispatch({type: LOGIN_ERROR, err});
});
// OR
// doesn't work either, throws the same error
/* firebase
.auth() <-- error is thrown
.signInWithEmailAndPassword(credentials.email, credentials.password)
.then(() => {
dispatch({type: LOGIN_SUCCESS});
})
.catch(err => {
dispatch({type: LOGIN_ERROR, err});
}); */
};
};
// It should return a promise instead of an error.
import firebase from 'firebase';
...
onLogin = async (user, success_callback, failed_callback) => {
await firebase.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then(success_callback, failed_callback);
}
worked for me

Axios not catching Network Offline

Challenge is to catch network offline status error during POST request using AXIOS library in React-Native.
axios.post(Constants.API.LOGIN, {
username: email,
password: password
})
.then(function (response) {
console.log('SUCCESS!');
loginUserSuccess(dispatch, user);
})
.catch(function (error) {
if(!error.response){
networkError(dispatch);
} else {
loginUserFail(dispatch);
}
});
but when I switch off WiFi getting error
Possible Unhandled Promise Rejection (id:0)
What is the way to handle network statuses with AXIOS?
Thanks!
Check out NetInfo:
import { NetInfo } from 'react-native'
https://facebook.github.io/react-native/docs/netinfo.html
isConnected
Available on all platforms. Asynchronously fetch a boolean to determine internet connectivity.
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
NetInfo.isConnected.removeEventListener(
'change',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'change',
handleFirstConnectivityChange
);
You may have a bit of extra issue there with your handling. I can't see exactly what is triggering the unhandled rejection in your code, but I can assume it is making it into the catch block.
If so, that currently means that either networkError() or loginUserFail() are failing, which is what is actually generating the unhandled rejection.
Take a look at those functions and make sure they have catch blocks inside them if they are asynchronous. Ask yourself where exactly is that error coming from, and answer that first, then look at NetInfo. Otherwise, the unhandled rejection may still be able to occur. Make sure you test all the different ways the code could fail :)
With NetInfo, you could set up something near the root-level in your app architecture, by way of that event listener. This could allow you to manage a setting in something like Redux. If the app detects offline, you could set the value to false and the entire app would know it is offline. You could probably make a middleware.
The code might look something like:
middleware:
`store.dispatch(changeConnectivityState())`
or you could have an action creator (shown with redux-thunk):
export function onAppConnectivityChange(newState) {
return (dispatch) {
dispatch({
type: APP_CONNECTIVITY_CHANGE,
payload: newState
})
}
}
Redux reducer:
case APP_CONNECTIVITY_CHANGE:
return {
...state,
isConnected: action.payload
}
In your components:
render() {
if (this.props.isConnected === true) {
return <View><Text>We are connected.</Text></View>
}
}
const mapStateToProps = (state) => {
const isConnected = state.someReducer.isConnected
return {
isConnected
}
}
export default connect(mapStateToProps, null)(SomeComponent)
Hopefully that gives you some ideas. Axios shouldn't be responsible for checking if the app is online or offline (except for quickly checking against a boolean). Your app should always know if it online or offline, and your components should be able to detect the state. You will find that much more scalable and easier to work with.
If I saw where you were calling Axios from, I could give you a more specific answer.

Returning Promise from action creator in React Native using redux-thunk

I have an action creator that is called from my React component:
// ...
import { connect } from 'react-redux';
// ...
import { submitProfile } from '../actions/index';
// ...
onSubmit() {
const profile = {
name: this.state.name
// ...
};
this.props.submitProfile(profile)
.then(() => { // I keep getting an error here saying cannot read property 'then' of undefined...
console.log("Profile submitted. Redirecting to another scene.");
this.props.navigator.push({ ... });
});
}
export default connect(mapStateToProps, { submitProfile })(MyComponent);
The definition of the action creator is something like the following. Note I am using the redux-thunk middleware.
export function submitProfile(profile) {
return dispatch => {
axios.post(`some_url`, profile)
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
// this doesn't seem to do anything . . .
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}
What I want to be able to do is call the action creator to submit the profile and then after that request was successful, push a new route into the navigator from my component. I just want to be able to determine that the post request was successful so I can push the route; otherwise, I would not push anything, but say an error occurred, try again.
I looked up online and found Promise.resolve(), but it doesn't not seem to solve my problem. I know that I could just do a .then after calling an action creator if I was using the redux-promise middleware. How do I do it with redux-thunk?
The return value from the function defined as the thunk will be returned. So the axios request must be returned from the thunk in order for things to work out properly.
export function submitProfile(profile) {
return dispatch => {
return axios.post(`some_url`, profile) // don't forget the return here
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}