Redux async actioncreator not recognizing then - react-native

I need to use .then() on a redux action, what is wrong in the following action?
export const userLogin = (username, password) => {
return dispatch => {
axios.post(`${TT_API_BASE}/Access/Login`, { username: username, password: password, applicationId: 2 }, {
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + auth,
}
})
.then(response => {
dispatch({
type: LOGIN,
payload: response.data
})
})
.catch(err => {
console.log(err)
dispatch({
type: LOGIN_FAILED
})
})
}
}
It is then called in a component like this
handlePress() {
this.props.userLogin(this.state.username, this.state.password)
.then(() => {
this.props.navigation.navigate('SelectInstance')
})
}
Which displays the errormessage that then is not defined. What am I doing wrong?

When you do dispatch(someThunkActionCreator()), the return value of dispatch is whatever your thunk function returns. So, you can only do dispatch().then() if the thunk function returns a promise.
Your thunk is making an AJAX call, but not actually returning a promise, so it actually returns undefined. Putting a return statement in front of axios.post() will return that promise and fix the problem.

Solved by doing this:
export const userLogin = (username, password) => {
return async dispatch => {
const onSuccess = data => {
dispatch({
type: LOGIN,
payload: data
})
}
const onError = err => {
dispatch({
type: LOGIN_FAILED
})
}
try {
const req = await axios.post(`${TT_API_BASE}/Access/Login`, { username: username, password: password, applicationId: 2 }, {
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization': 'Basic ' + auth,
}
})
return onSuccess(req.data)
}
catch (err) {
return onError(err)
}
}
}

Related

data sent with vuex action return undefined

i'm using axios with vuex, i need to send data with json form to execute post request and add new row with axios, i'm using vuex, when action is trigered it doesn't keep the data and sent it on action, the data json is created on vue componment but don't send it to action to execute axios post :
Action.js:
export const addClassification = ({data}) => {
console.log('data actio:', {data})
axios
.post("/vendor/classification/save", data, {
headers: {
"Content-Type": "application/json",
// withCredentials: true //sent with cookie
Authorization: "Bearer " + Vue.$cookies.get("jwt"),
}
})
.then((res) => {
console.log('res', res)
// commit("ADD_TO_CLASSIFICATION", data);
})
.catch((err) => {
console.log(err);
});
state.js:
export default {
vendorClassificationList: [],
}
page.vue:
<BaseButton
label="Modifier"
classValue="btn-outline-primary"
#click.native="addClassificationData"
/>
data() {
return {
submitStatus: "",
name: this.$route.params.name,
id: this.$route.params.id,
code: this.$route.params.code,
jsonData:[]
};
},
methods: {
...mapActions(["addClassification"]),
addClassificationData() {
this.jsonData = JSON.stringify({
id: null,
name: this.name,
code:this.code,
active:true
})
console.log('json is', this.jsonData)
this.addClassification({
data : this.jsonData
})
},
Actions is Vuex receive the vuex context as the first param, as you can see in the docs.
In other words if you change in Action.js:
addClassification = ( {data}) => {
to
addClassification = (vuexContext, {data}) => {
it should do the trick. You can call the param vuexContext, context, destructure it if needed or call it _ if unused (as in your case), doesn't really matter, as long as it's there.
Your vuex action is wrong. You are missing the context which can use argument restructuring. Also, you probably need to send res.data within the commit instead of res, depending on what are you doing in your mutation.
actions: {
addClassification ({ commit }, payload) {
axios
.post("/vendor/classification/save", payload, {
headers: {
"Content-Type": "application/json",
// withCredentials: true //sent with cookie
Authorization: "Bearer " + Vue.$cookies.get("jwt"),
}
})
.then((res) => {
console.log('res', res)
commit("ADD_TO_CLASSIFICATION", res.data);
})
.catch((err) => {
console.log(err);
})
}
}

Getting a 401 error when trying to create a new post

I am trying to create a post using an app built in react native but everytime I try creating it gives me a 401 error after I have already logged in. I assume it isn't getting a token from AsyncStorage. I need helping.
This is the ItemContext where the functionality for creating a post-
import createDataContext from "./createDataContext";
import sellerApi from "../api/seller";
import { navigate } from "../navigationRef";
const itemReducer = (state, action) => {
switch (action.type) {
case "fetch_items":
return action.payload;
case "create_item":
return { errorMessage: "", item: action.payload };
default:
return state;
}
};
const fetchItems = dispatch => async () => {
const response = await sellerApi.get("/api/items");
console.log(response.data);
dispatch({ type: "fetch_items", payload: response.data });
};
const createItem = dispatch => async (
title,
category,
detail,
condition,
price
) => {
try {
const response = await sellerApi.post("/api/items", {
title,
category,
detail,
condition,
price
});
//this is the other place the error might be happening i need this to save in the phone local storage
console.log(response.data);
dispatch({ type: "create_item", payload: response.data });
navigate("Home");
} catch (err) {
console.log(err);
}
};
export const { Provider, Context } = createDataContext(
itemReducer,
{ createItem, fetchItems },
[]
);
this is the AuthContext where the signin and signup functionality is located and the AsyncStorage is used. Let me know if you guys need to see the node function for Auth.
import createDataContext from "./createDataContext";
import sellerApi from "../api/seller";
import { navigate } from "../navigationRef";
const authReducer = (state, action) => {
switch (action.type) {
case "add_error":
return { ...state, errorMessage: action.payload };
case "signup":
return { errorMessage: "", token: action.payload };
case "signin":
return { errorMessage: "", token: action.payload };
case "fetch_user":
return action.payload;
case "clear_error_message":
return { ...state, errorMessage: "" };
case "signout":
return { token: null, errorMessage: "" };
default:
return state;
}
};
const tryLocalSignin = dispatch => async () => {
const token = await AsyncStorage.getItem("token");
if (token) {
dispatch({ type: "signin", payload: token });
navigate("Home");
} else {
navigate("loginFlow");
}
};
const clearErrorMessage = dispatch => {
dispatch({ type: "clear_error_message" });
};
const signup = dispatch => async ({ name, phone, email, password }) => {
try {
const response = await sellerApi.post("/api/users", {
name,
phone,
email,
password
});
//this is the other place the error might be happening i need this to save in the phone local storage
await AsyncStorage.setItem("token", response.data);
console.log(response.data);
dispatch({ type: "signup", payload: response.data.token });
navigate("Home");
} catch (err) {
dispatch({ type: "add_error", payload: "FAIL" });
}
};
const signin = dispatch => async ({ email, password }) => {
try {
const response = await sellerApi.post("/api/auth", {
email,
password
});
await AsyncStorage.setItem("token", response.data);
console.log(response.data);
dispatch({ type: "signin", payload: response.data.token });
navigate("Home");
} catch (err) {
dispatch({ type: "add_error", payload: "FAIL" });
}
};
// const fetchUser = dispatch => async () => {
// const response = await sellerApi.get("/auth");
// dispatch({ type: "fetch_user", payload: response.data });
// };
//need to get the users info to display it in the accountScreen
const signout = dispatch => async () => {
await AsyncStorage.removeItem("token");
dispatch({ type: "signout" });
navigate("loginFlow");
};
export const { Provider, Context } = createDataContext(
authReducer,
{ signup, signin, signout, tryLocalSignin },
{ token: null, errorMessage: "" }
);
This is the backend for the Auth function that makes sure the user is logged in before begin able to send a post request----
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = function (req, res, next) {
const token = req.header("x-auth-token");
if (!token) return res.status(401).send("Access denied");
try {
const decoded = jwt.verify(token, config.get("jwtPrivateKey"));
req.user = decoded;
next();
} catch (ex) {
res.status(400).send("Invalid token.");
}
}
this is where the post request for when you signup and login is pretty much similar-
router.post("/", async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (user) return res.status(400).send("User already registered.");
user = new User(_.pick(req.body, "name", "phone", "email", "password"));
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
await user.save();
const token = user.generateAuthToken();
res.header("x-auth-token", token).send(token);
});
PLEASE HELP
Importing Async storage like this import {AsyncStorage} from 'react-native'; has been deprecated. You can check here async storage .
Thats why i suppose the AsyncStorage is not working, try downloading this rn-community-async-storage . package first and then import AsyncStorage like
import AsyncStorage from '#react-native-community/async-storage';
hope it helps. feel free for doubts

await is giving Refrence error in react-native

I have the following code
async _onPress() {
NetInfo.isConnected.fetch().then(isConnected => {
if (isConnected) {
fetch(apiURL + '/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.props.username,
password: this.props.password
})
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.status === 'success')
//I am getting error here
await AsyncStorage.setItem('token', responseJson.token);
//moving on to the main screen if server return success
Actions.MainScreen();
} else {
Toast.show({
text: "Wrong username or password!",
type: "danger"
});
}
})
.catch((error) => {
console.log(error);
});
} else {
Toast.show({
text: "Please Connect To Internet",
type: "danger"
});
}
});
}
I am trying to save the token i receive from my API server using AsyncStorage.I get the following errors
Expression statement is not assignmentor call
and RefrenceError:await is not defined
when i try to use await in the location.
But when use the same code at the beginning of the function i get no errors.
I dont know what is wrong. Is it not allowed in async await? I am not too familiar with these.
If _onPress calls some async functions and you want to wait for all of them to finish, you must place an 'await' in front of every single one of them. In addition, every async function, including async callback functions, must be declared async too.
The complete code:
async _onPress() {
**await** NetInfo.isConnected.fetch().then(**async** (isConnected) => {
if (isConnected) {
**await** fetch(apiURL + '/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.props.username,
password: this.props.password
})
})
.then((response) => response.json())
.then(**async** (responseJson) => {
if (responseJson.status === 'success') {
**await** AsyncStorage.setItem('token', responseJson.token);
Actions.MainScreen();
} else {
Toast.show({
text: "Wrong username or password!",
type: "danger"
});
}
})
}
})
}

How to update view inside axios promise and after store dispatch?

I have a simple vue app where I'm trying to add simple authentication. Inside my login.vue, I use axios to authenticate the user via ajax and store the token returned by the api in the store then redirect to a new page (ex: dashboard.vue).
The problem is that the token is saved but the view is not updated, can't call router.push() ...
Any ideas why isn't it working ? Thanks
Login.vue
methods: {
authenticate () {
var dataLogin = {
email: this.login,
password: this.password
}
var headers = { headers: { 'Content-type': 'application/json', 'Accept': 'application/json' } }
axios.post(config.apiUrl, dataLogin, headers)
.then(response => {
this.$store.dispatch('login', response.data).then(() => {
// if there is no error go to home page
if (!this.$store.getters.error) {
this.$router.push('/')
}
})
})
.catch(error => {
this.errorMessage = error.response.data.message
this.authError = true
})
}
}
The store function just save the token with localStorage
const actions = {
login (context, data) {
context.commit('authenticate', data)
}
}
const mutations = {
authenticate (state, data) {
localStorage.setItem('user-access_token', data.access_token)
}
}
You are calling a then() handler when you dispatch the action.
But your action does not return a promise.
So return a promise in your action as follows:
const actions = {
login (context, data) {
return new Promise((resolve, reject) => {
context.commit('authenticate', data)
resolve()
})
}
}
Also chain your promises for better readability
axios.post(config.apiUrl, dataLogin, headers)
.then(response => {
return this.$store.dispatch('login', response.data)
}).then(() => {
// if there is no error go to home page
if (!this.$store.getters.error) {
this.$router.push('/')
}
})
.catch(error => {
this.errorMessage = error.response.data.message
this.authError = true
})

Only navigate to next page when asynchronos actions are complete? React-native

So, I have a bit of a tricky situation here for me as a beginner with redux as well as react-native.
When the user loggs in, I want to update the Redux state with the user data. I call a login methond where I get a web token. Directly afterwards I want to dispatch two asynchronous actions with redux-thunk. The problem is:
By the time these actions are dispatched and I have the response from the API, I've already navigated to another screen and the data to render the list is not in the Redux state.
The Question: How can I "hold" the program until my state is updated and then navigate to the next page?
This is what happens when the user logs in:
fetch("http://10.0.2.2:8000/api/api-token-auth/", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: this.props.email,
password: this.props.password,
})
}).then((response) => response.json()
).then((jResponse) => {
console.log(jResponse);
this._onValueChange('token_id', jResponse.token);
this.props.loginUserSuccess();
this.props.navigation.navigate('MainMenue');
}).catch((error) => {
console.log(error);
this.props.loginUserFail();
})
}
Somewhere during the login these two actions sould be dispatched completly and the state should be updated:
export const profileLoad = () => {
return (dispatch) => {
AsyncStorage.getItem('token_id')
.then((token_id) => fetch("http://10.0.2.2:8000/api/profile/", {
method: "GET",
headers: {
'Authorization': 'JWT ' + token_id
}
})
.then((response) => response.json())
.then((answer) => {
dispatch({ type: PROFILE_LOAD, payload: answer});
})
.done());
}
}
export const productsLoad = () => {
return (dispatch) => {
AsyncStorage.getItem('token_id')
.then((token_id) => {
fetch("http://10.0.2.2:8000/api/profile/products/", {
method: "GET",
headers: {
'Authorization': 'JWT ' + token_id
}
}).then((anser) => anser.json())
.then((response)=> {
dispatch ({ type: PRODUCTS_LOAD, payload: response})
})
}
).done();
}
}
Then I want to navigate the another screen andrender a list (with ListView) to display the JSON data from products and profiles.
-- > So I finally figured it out.
Solution
1.) Return promises from action creators as stated
2.) Make sure you put a callback function in the then method
export const loadAllProfileData = ({navigate}) => {
return (dispatch) => {
dispatch(profileLoad())
.then(() => dispatch(productsLoad()))
.then(() => navigate('MainMenue'))
};
}
export const profileLoad = () => {
return (dispatch) => {
return AsyncStorage.getItem('token_id')
.then((token_id) => fetch("http://10.0.2.2:8000/api/profile/", {
method: "GET",
headers: {
'Authorization': 'JWT ' + token_id
}
})
).then((response) => response.json())
.then((answer) => {
dispatch({ type: PROFILE_LOAD, payload: answer});
})
}
}
export const productsLoad = () => {
return (dispatch) => {
return AsyncStorage.getItem('token_id')
.then((token_id) =>
fetch("http://10.0.2.2:8000/api/profile/products/", {
method: "GET",
headers: {
'Authorization': 'JWT ' + token_id
}
})
).then((answer) => answer.json())
.then((response)=> {
dispatch ({ type: PRODUCTS_LOAD, payload: response})
})
}
}
You can return promises from your action creators and chain them with then. You can do that by simply adding return AsyncStorage.getItem() ... to your action creators. Then you can do:
fetch(url) //login
.then(dispatch(profileLoad))
.then(dispatch(productsLoad))
.then(this.props.navigation.navigate('MainMenue'))
.catch(err => //handle error)
Read more about promises chaining.
Edit: A simple example would be:
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import fetch from 'node-fetch';
const ROOT_URL = 'https://jsonplaceholder.typicode.com';
const FETCH_DATA = 'FETCH_DATA';
const url = `${ROOT_URL}/users`;
function fetchData() {
return (dispatch) => {
return fetch(url)
.then(res => res.json())
.then(data => {
dispatch({
type: FETCH_DATA,
payload: data[0].name
});
})
}
}
function reducer(state = [], action) {
if (action.type === FETCH_DATA) {
console.log('Action.payload:', action.payload);
}
switch (action.type) {
case 'FETCH_DATA':
return [...state, action.payload];
default:
return state;
};
}
let store = createStore(
reducer,
applyMiddleware(thunkMiddleware)
)
store.subscribe(() =>
console.log('Store State: ', store.getState())
)
fetch(url)
.then(res => res.json())
.then(data => data)
.then(store.dispatch(fetchData()))
.then(store.dispatch(fetchData()))