Only navigate to next page when asynchronos actions are complete? React-native - 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()))

Related

seting auth token in react native not working

i am trying to set auth token in react native but it is not working.the api call to the url is woeking and data is saved to db but the token doesnot work
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/register',
data: Data,
})
.then(function (response) {
console.log('working');
ReactSession.setStoreType('Bearer', response.data.token);
ReactSession.set('username', 'Meon');
})
.catch(error => {
alert(JSON.stringify(error.response.data));
});
}
i get this error
console.log(response); returns the following
I use AsyncStorage together with fetch to set mine and then when i want to use it , I also call AsyncStorage from '#react-native-async-storage/async-storage';
After setting the state like this,
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
I try to simulate a Login
To login looks like this :
FunctionLogin = async () => {
let item = {email, password};
fetch('http://192.168.1.101/api/auth/sign-in', {
method: 'POST',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify(item),
})
.then(response => response.json())
.then(async (responseJson) => {
if (responseJson.message === 'OK') {
var token = responseJson.token;
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('token', token);
navigation.replace('Dashboard');
} else {
alert(responseJson);
}
})
.catch(error => {
console.error(error);
});
}
To use it in any page, I use it like this , later i reference the function in useEffect
showdata = async () => {
let token = await AsyncStorage.getItem('token');
alert(token);
};
Suppose I want to get transaction list from my endpoint to display data I do it like this
getTransactionsList = async () => {
let token = await AsyncStorage.getItem('token');
let email = await AsyncStorage.getItem('email');
var url = 'https://192.168.1.101/api/user-data/get-transactionby-email/';
fetch(url + email, {
method: 'GET',
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`,
},
})
.then(response => response.json())
.then(responseJson => {
setTransaction_details(responseJson);
setLoading(false);
});
};
Then suppose i want to call it inside useEffect, I do like this
useEffect(() => {
getTransactionsList();
});
Thats what and how i do it and it works fine. If you also know how to use Redux, its still a good one as well.

How do I use Async Storage to save Data Locally after calling fetch in react native?

I want to use Async storage. Each time I call without the async function like this
FunctionLogin = () =>{ //other methods here ........ }
and this does not have await anywhere, it saves to the database but when i use let email = AsyncStorage.getItem('email'); to call it back, it does not return anything like the email just [Object object] is what i see
how do I resolve this
the fetch method to save to async storage looks like this
`FunctionLogin = async () =>{
//navigation.replace('VirtualAccountPage');
let item = {email, password,phone};
fetch('https://xxxxxxxxxxxxxxxx/api/sign-up', {
method: 'POST',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify(item),
})
.then(response => response.json())
.then(responseJson =>{
if (responseJson.message === 'User created Successfully') {
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('phone', phone);
alert('I am Registered');
navigation.replace('VirtualAccountPage');
} else {
alert(responseJson);
}
})
.catch(error => {
console.error(error);
});
}`
the function to call it back, so it can be used as persistence looks thus
` FunctionUserDetails = () => {
let email = AsyncStorage.getItem('email');
let phone = AsyncStorage.getItem('telephone');
//navigation.replace('Dashboard');
alert(email);
};`
How do i get this to work?
I want to be able to save data locally using async storage so i can be able to persist the data on some other screens etc. I tried several things to see if It could work as expected, i do not get to see it work as i want.
to get the value from AsyncStorage you need to use await and the function should start with async
fetch('https://xxxxxxxxxxxxxxxx/api/sign-up', {
method: 'POST',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify(item),
})
.then(response => response.json())
.then(async (responseJson) =>{ // add async here
if (responseJson.message === 'User created Successfully') {
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('phone', phone);
alert('I am Registered');
navigation.replace('VirtualAccountPage');
} else {
alert(responseJson);
}
})
.catch(error => {
console.error(error);
});
const FunctionUserDetails = async () => { // change this
let email = await AsyncStorage.getItem('email'); // change this
let phone = await AsyncStorage.getItem('telephone'); // change this
//navigation.replace('Dashboard');
alert(email);
};`
Install this updated async-storage npm
Try implementing using below code:
fetch('https://xxxx/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-type': 'application/json',
},
body: JSON.stringify(item),
})
.then(response => response.json())
.then(async (responseJson) =>{ // add async here
if (responseJson.stausCode === 200) {
await AsyncStorage.setItem('name', name);
} else {
alert(responseJson);
}
})
.catch(error => {
console.error(error);
});

I am unable to get API data in redux store

I am trying to build an app to add and delete items. I am using an API(Link of API documentation below). I can post and get data from API to the store. But I am unable to show the saved items on UI. And the getBooks function seems to be not working. Can anyone please help me?
Link to API documentation: https://www.notion.so/Bookstore-API-51ea269061f849118c65c0a53e88a739
Here is the code, I have used.
export const addBook = (book) => async (dispatch) => {
await fetch(url, {
method: 'POST',
body: JSON.stringify(book),
headers:{
'Content-type': 'application/json; charset=UTF-8',
}
})
.then(() => dispatch({type: ADD_BOOK, book}))
}
export const removeBook = (index) => async (dispatch) => {
await fetch(`${url}/${index}`, {
method: 'DELETE',
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
.then(() => dispatch({ type: REMOVE_BOOK, index }));
};
export const getBooks = () => async (dispatch) => {
await fetch(url)
.then((res) => res.json())
.then((book) => {
const booksArray = [];
Object.keys(book).forEach((key) => {
booksArray.push({
item_id: key,
author: book[key][0].author,
title: book[key][0].title,
category: book[key][0].category,
});
});
dispatch({ type: GET_BOOKS, booksArray});
});
};

Redux async actioncreator not recognizing then

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

VueJs. How to close pre-loader after data from server have been loaded

I use VueX in my VueJs app and I need to close pre-loader after I got an answer from server for 4 my get requests. I try to use callback function to change pre-loader state but it changes after requests STARTs, but I need to change pre-loader state after all requests SUCCESS. Below is my code:
Index.vue
<template>
<div class="index">
<div class="content-is-loading"
v-if="appIsLoading"></div>
<div v-else class="index__wrapper">
<navbarInner></navbarInner>
<div class="index__content">
<sidebar></sidebar>
<router-view></router-view>
</div>
<foo></foo>
</div>
</div>
</template>
<script>
import NavbarInner from './NavbarInner'
import Sidebar from './Sidebar'
import Foo from './../Foo'
import Shows from './Shows/Shows'
import Dashboard from './Dashboard'
import { API_URL } from '../../../config/constants'
import { mapState } from 'vuex'
export default {
name: 'index',
data () {
return {
appIsLoading: true,
bandName: ''
}
},
components: {
NavbarInner,
Sidebar,
Foo,
Shows,
Dashboard
},
created () {
function loadData (context, callback) {
// Loading bands for the user
context.$store.dispatch('getBands')
// Loading contacts for the user
context.$store.dispatch('getContacts')
// Loading merch for the user
context.$store.dispatch('getInventory')
// Loading tours for the active band
context.$store.dispatch('getToursList')
callback(context)
}
loadData(this, function (context) {
context.appIsLoading = false
})
}
}
Below I add code of one of the request:
api/tour.js
import axios from 'axios'
import { API_URL } from '../../config/constants'
export default {
getToursList () {
return new Promise((resolve, reject) => {
let bandId = window.localStorage.getItem('active_band_id')
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/bands/' + bandId + '/tours/', {
headers: {'x-access-token': token}
})
.then((result) => {
return resolve(result.data)
})
.catch(err => reject(err))
})
},
getInventory () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/merch/listProductForUser/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
let items = response.data
return resolve(items)
})
.catch((err) => {
return reject(err)
})
})
},
getContacts () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/contact/get_contacts_for_user/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
console.log(response.data)
let contacts = response.data
return resolve(contacts)
})
.catch((err) => {
return reject(err)
})
})
},
getBands () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/band/getBandsForUser/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
console.log(response.data)
let bands = response.data
return resolve(bands)
})
.catch((err) => {
return reject(err)
})
})
}
}
Vuex/tour.js
import api from '../../api/onload'
import * as types from '../mutation-types'
const state = {
tours: [],
contacts: [],
bands: [],
merch: [],
success: false,
loading: false
}
const actions = {
getToursList ({commit}) {
api.getToursList()
.then((tours) => {
commit(types.RECEIVE_TOURS, tours)
}).catch((err) => {
console.error('Error receiving tours: ', err)
commit(types.RECEIVE_TOURS_ERROR)
})
},
getInventory ({commit}) {
api.getInventory()
.then((items) => {
commit(types.RECEIVE_INVENTORY, items)
})
.catch((err) => {
console.error('Error receiving inventory: ', err)
commit(types.RECEIVE_INVENTORY_ERROR)
})
},
getBands ({commit}) {
api.getBands()
.then((bands) => {
commit(types.RECEIVE_BANDS, bands)
})
.catch((err) => {
console.error('Error receiving bands: ', err)
commit(types.RECEIVE_BANDS_ERROR)
})
},
getContacts ({commit}) {
api.getContacts()
.then((contacts) => {
commit(types.RECEIVE_CONTACTS, contacts)
})
.catch((err) => {
console.error('Error receiving bands: ', err)
commit(types.RECEIVE_CONTACTS_ERROR)
})
}
}
const mutations = {
[types.RECEIVE_TOURS] (state, tours) {
state.tours = tours
},
[types.RECEIVE_INVENTORY] (state, items) {
state.items = items
},
[types.RECEIVE_BANDS] (state, bands) {
state.bands = bands
},
[types.RECEIVE_CONTACTS] (state, contacts) {
state.contacts = contacts
console.log(state.contacts)
}
}
export default {
state, mutations, actions
}
How should I change the code?
The code you posted doesn't actually wait on the response from any of the actions you are calling.
You could also move everything to a method and refactor.
Finally I've assumed your actions return a Promise i.e.
created () {
this.getAll()
},
methods: {
getAll () {
Promise.all([
this.$store.dispatch('getBands'),
this.$store.dispatch('getContacts'),
this.$store.dispatch('getInventory'),
this.$store.dispatch('getToursList'),
])
.then(responseArray => {
this.appIsLoading = false
})
.catch(error => { console.error(error) })
EDIT
To get your actions to resolve as you need them (when the mutations have fired and your store is updated) you need to wrap them in a Promise:
Vuex/tour.js (actions object)
getToursList: ({ commit }) =>
new Promise((resolve, reject) => {
api.getToursList()
.then((tours) => {
commit(types.RECEIVE_TOURS, tours)
resolve()
}).catch((err) => {
console.error('Error receiving tours: ', err)
commit(types.RECEIVE_TOURS_ERROR)
reject()
})
})