Asyncstorage in Redux action - react-native

I'm trying to use my access token (stored in Asyncstorage) in a Redux action. This is my code:
export function fetchData() {
const endpoint = 'someEndpoint.com';
let accessToken = '';
myAccessToken().then((token) => {
accessToken = token;
});
return (dispatch) => {
dispatch(getData());
axios.get(endpoint, { headers: { 'access-token': accessToken } })
.then(response => {
dispatch(getDataSuccess(response.data));
})
.catch(error => {
dispatch(getDataFailure(error));
});
};
}
const myAccessToken = async () => {
try {
const retrievedItem = await AsyncStorage.getItem('accessToken');
return retrievedItem;
} catch (error) {
return null;
}
};
But fetching of the key is obviously async, I'm not sure on how to use the accessToken in the API call. I'm not allowed to do something like this:
export function fetchData() {
const endpoint = 'someEndpoint.com';
myAccessToken().then((token) => {
return (dispatch) => {
dispatch(getData());
axios.get(endpoint, { headers: { 'access-token': token } })
.then(response => {
dispatch(getDataSuccess(response.data));
})
.catch(error => {
dispatch(getDataFailure(error));
});
};
});
}
My Store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import app from './reducers';
export default function configureStore() {
return createStore(app, applyMiddleware(thunk));
}
Update
In the end I did it a little bit different, In my componentDidMount:
componentDidMount() {
AsyncStorage.getItem('accessToken').then((accessToken) => {
this.setState({ accessToken });
this.props.fetchData(accessToken);
});
}
Thanks, Kevin.

I think you should use redux-thunk library for asynchronous updates of the redux state. It's easy to configure in the store.js file:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Then, I would implement it like this:
export function fetchData() {
return async function(dispatch) {
const endpoint = 'someEndpoint.com';
const accessToken = await myAccessToken();
try {
const response = await axios.get(endpoint, { headers: { 'access-token': accessToken } });
return dispatch(getDataSuccess(response.data));
} catch (error) {
return dispatch(getDataFailure(error));
}
}
}

Related

How I can call NuxtServerInit correctly?

When i try to call rest api with nuxtServerInit on Vuex store it don't call, but if call rest api on components or page it works.
store/index.js
import axios from 'axios'
export const state = () => ({
news: [],
})
export const mutations = {
SET_NEWS(state, posts) {
state.news = posts
}
}
export const actions = {
async nuxtServerInit({ commit }, ctx) {
const res = await axios.get('https://api/news.json')
commit('SET_NEWS', res.data)
},
}
export const getters = {
getNews(state) {
return state.news
}
}
pages/news/index.vue
computed: {
getNews() {
return this.$store.getters.getNews
}
}
Try calling your API like this in your vuex actions:
export const actions = {
async nuxtServerInit({ commit }, { req }) {
const res = (await this.$axios.$get('https://api/news.json')).data
commit('SET_NEWS', res)
},
}

Redux Saga action async action called once but fired twice

So I dispatch my redux-saga action once from my react-native app and it makes two API calls. I'm trying to figure out why this is, and how to only have it send one.
App.js
const initFetch = async () => {
const userToken = await AsyncStorage.getItem("userToken");
dispatch(fetchLiked({ page: 0, search: "", userToken }));
};
useEffect(() => {
initFetch();
}, []);
configureStore.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import AsyncStorage from "#react-native-community/async-storage";
import likedReducer from "./reducers/liked";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas/rootSaga";
const rootReducer = combineReducers({
liked: likedReducer,
});
const persistConfig = {
key: "primary",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const sagaMiddleware = createSagaMiddleware();
export default () => {
let store = createStore(persistedReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
let persistor = persistStore(store);
return { store, persistor };
};
rootSaga.js
import { take, call, all } from "redux-saga/effects";
import { watchFetchLikedSaga } from "./likedSaga";
export default function* rootSaga() {
yield all([watchFetchLikedSaga()]);
}
likedSaga.js
import { takeLatest, call, put } from "redux-saga/effects";
import Server from "../../utils/Server";
import { fetchLikedSuccess } from "./../actions/liked";
import { types } from "../actions/types";
function* asyncFetchLiked(data) {
console.log("sending async fetch");
const { page, search, userToken } = data.payload;
try {
const response = yield call(() =>
Server.get("/api/titles/getliked", {
headers: { "auth-token": userToken },
params: { page: page, search: search },
})
);
yield put(fetchLikedSuccess(response.data));
} catch (e) {
console.log(e);
}
}
export function* watchFetchLikedSaga() {
yield takeLatest(types.SEND_REQUEST, asyncFetchLiked);
}
export const fetchLiked = (data) => {
return {
type: types.SEND_REQUEST,
payload: data,
};
};
actions/liked.js
export const fetchLiked = (data) => {
console.log("fetchLiked");
return {
type: types.SEND_REQUEST,
payload: data,
};
};
export const fetchLikedSuccess = (data) => {
console.log("fetchLikedSuccess");
return {
type: types.SEND_REQUEST_SUCCESS,
payload: data,
};
};
export const fetchLikedFailure = (error) => {
return {
type: types.SEND_REQUEST_FAILURE,
payload: {},
error: error,
};
};
My console.log output looks like this. You can see the action is only being dispatched once, but it is sending two async requests and calling the reducer success action twice.
fetchLiked
sending async fetch
sending async fetch
fetchLikedSuccess
fetchLikedSuccess

Action Creator return undefined axios

Can successfully register the user using my action creator but it returns undefined. I think it's the way how am returning my dispatch
axiosInstance
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
// import base url
import {API_URL} from '../constants';
const instance = axios.create({
baseURL: API_URL,
timeout: 2000,
});
instance.interceptors.request.use(
async(config) => {
const token = await AsyncStorage.getItem('token');
if(token) {
config.headers.Autherization = `${token}`;
}
return config;
},`enter code here`
(err) => {
return Promise.reject(err);
}
)
export default instance;
SignUP Action.
import axiosInstance from '../../api/axiosInstance';
import {REGISTER_USER_SUCCESS, REGISTER_USER_FAIL} from '../actionTypes/index';
const registerSuccess = (payload) => {
return{
type: REGISTER_USER_SUCCESS,
data: payload
}
};
const registerError = (payload) => {
return {
type: REGISTER_USER_FAIL,
data: payload
}
};
export const SignUp = (registerData) => async dispatch => {
axiosInstance.post('/users/register', registerData)
.then((response)=> {
dispatch(registerSuccess(response.data));
})
.catch((error) => {
dispatch(registerError(error));
});
}
Here is how am using my action creator .. the result is undefined . I want to have a check some that I can redirect the screen to another login screen or home screen
SignUP Submit function
dispatch(registerAction.SignUp(values))
.then( (result) => {
console.log('klhadsghaj',result.status);
if(result.success) {
try {
navData.navigation.navigate("Login");
}catch (err) {
console.log(err)
}
} else {
Alert.alert('Registration failed. Try Again')
}
})
.catch(err => console.log(err))

How can I test actions within a Vuex module?

I want to test a vuex module called user.
Initially, I successfully registered my module to Vuex. Its works as expected.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
}
})
export default store
My user module is defined as follows
store/modules/user.js
const state = {
token: getToken() || '',
}
export const getters = {
token: state => state.token,
}
const mutations = {
[SET_TOKEN]: (state, token) => {
state.token = token
}
}
const actions = {
[LOGIN] ({ commit }, body) {
return new Promise((resolve, reject) => {
login(body).then(response => { //login is an api method, I'm using axios to call it.
const { token } = response.data
setToken(token)
commit(SET_TOKEN, token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
getters,
mutations,
actions
}
login api
api/auth.js
import request from '#/utils/request'
export function login (data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
axios request file
utils/request
import axios from 'axios'
import store from '#/store'
import { getToken } from '#/utils/auth'
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 5000
})
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers['Authentication'] = token
}
return config
}
)
export default request
When I want to write some test (using Jest), for example login action as shown above.
// user.spec.js
import { createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import actions from '#/store/modules/user'
const localVue = createLocalVue()
localVue.use(Vuex)
test('huhu', () => {
expect(true).toBe(true)
// implementation..
})
How can I write test for my Login action? Thanks. Sorry for my beginner question.
EDIT: SOLVED Thank you Raynhour for showing to me right direction :)
import { LOGIN } from '#/store/action.types'
import { SET_TOKEN } from '#/store/mutation.types'
import { actions } from '#/store/modules/user'
import flushPromises from 'flush-promises'
jest.mock('#/router')
jest.mock('#/api/auth.js', () => {
return {
login: jest.fn().mockResolvedValue({ data: { token: 'token' } })
}
})
describe('actions', () => {
test('login olduktan sonra tokeni başarıyla attı mı?', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions[LOGIN](context, body)
await flushPromises()
expect(context.commit).toHaveBeenCalledWith(SET_TOKEN, 'token')
})
})
Store it's just a javascript file that will export an object. Not need to use vue test util.
import actions from '../actions'
import flushPromises from 'flush-promises'
jest.mock('../api/auth.js', () => {
return {
login: jest.fn()..mockResolvedValue('token')
}; // mocking API.
describe('actions', () => {
test('login should set token', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions.login(context, body)
await flushPromises() // Flush all pending resolved promise handlers
expect(context.commit).toHaveBeenCalledWith('set_token', 'token')
})
})
but you need to remember that in unit tests all asynchronous requests must be mocked(with jest.mock or something else)

How can I properly test my React Native OAuth wrapper component?

I have written a React Native "Auth Portal" component, that links with an existing OAuth portal and handles getting the auth-code from the redirect URI and the subsequent token exchange request. It seems to be working well, but clearly I need to test this assumption, so I am trying to write unit/functional tests. How can I properly do this?
I originally considered extracting the functions used in the two useEffects out into separate, isolated functions and taking, for example, the authCode as an argument instead of from state and mocking this input.
However, I believe a better strategy is to test the component as a whole and just mock the response to the axios post request, comparing that mock to what get's stored in the AsyncStorage, as well as mocking a bad request/response to test the error handling.
Is this a good approach?
import axios from 'axios'
import AsyncStorage from '#react-native-community/async-storage'
import React, { useEffect, useState } from 'react'
import { Linking } from 'react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import { LoadingIndicator } from '../LoadingIndicator'
interface AuthPortalProps {
client_id: string
scopes: string[]
client_secret: string
redirect_uri: string
onAuthComplete: () => void
onError: () => void
}
interface ApiDataResponse {
token_type: string
expires_in: number
access_token: string
refresh_token: string
}
export const AuthPortal = ({
client_id,
scopes,
client_secret,
redirect_uri,
onAuthComplete,
onError,
}: AuthPortalProps) => {
const [authCode, setAuthCode] = useState()
const getAuthCodeFromRedirectUri = async (url: string) => {
if (url.includes('code=')) {
const regex = /[^=]+$/g
const code = url.match(regex)!.toString()
await setAuthCode(code)
}
}
useEffect(() => {
const getAuthCode = async () => {
const url = `https://example.com/auth/?response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${scopes}`
if (!authCode) {
try {
InAppBrowser.openAuth(url, redirect_uri).then(response => {
if (response.type === 'success' && response.url && response.url.includes('code=')) {
getAuthCodeFromRedirectUri(response.url)
Linking.openURL(redirect_uri)
}
})
} catch (error) {
console.log('Error: ', error.message)
onError()
}
}
}
getAuthCode()
return () => {
InAppBrowser.closeAuth()
}
}, [authCode, client_id, onError, redirect_uri, scopes])
useEffect(() => {
const getAuthRefreshToken = async () => {
if (authCode) {
try {
const { data }: { data: ApiDataResponse } = await axios.post(
'https://example.com/auth',
{
grant_type: 'authorization_code',
client_id: `${client_id}`,
code: `${authCode}`,
client_secret: `${client_secret}`,
redirect_uri: `${redirect_uri}`,
}
)
await Promise.all([
AsyncStorage.setItem('access_token', data.access_token),
AsyncStorage.setItem('refresh_token', data.refresh_token),
])
setTimeout(() => {
onAuthComplete()
}, 1000)
} catch (error) {
if (error.response) {
console.log('Error: ', error.response)
} else if (error.request) {
console.log('Error: ', error.request)
} else {
console.log('Error: ', error.message)
}
onError()
}
}
}
getAuthRefreshToken()
}, [authCode, client_id, client_secret, onAuthComplete, onError, redirect_uri])
return <LoadingIndicator />
}