Redirect to a Screen after async action on React-Native Redux Application using redux-thunk - react-native

I'm integrating redux(redux version - 4.0.x, react-redux version - 7.1.x) to an existing react-native(version - 0.60.5) application with react-navigation(version - 4.0.x) and revamping the existing backend Http requests from Axios to fetch API in redux action creators using redux-thunk middleware(version - 2.3.x).
I was unable to figure out how to setup react-navigation to redirect to another screen when in the success function of the fetch call.
Here I'm not going to handover the navigation to redux using react-navigation/redux-helpers but need to keep the react-navigation to manage the navigation of the application
Example Fetch Call in Redux Action Creator(Login):
const fetchLogin = (username, password) => {
const url = hostUrl + '/oauth/token';
return(dispatch) => {
dispatch(getLoginAttempt(true))
return(fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body:"client_secret&username="+username+"&password="+password
}))
.then((response) => {
console.log("Response", response);
if(response.status >= 200 && response.status < 300) {
dispatch(getLoginAttempt(false))
response.json()
.then((responseJson) => {
console.log("responseJSON", responseJson);
dispatch(getLoginAttemptSuccess(responseJson))
try{
setAccessToken(responseJson.access_token);
setAccessToken5(responseJson.access_token);
} catch (e) {
console.log("Error Saving the Access Token");
}
})
} else {
response.json()
.then((responseJson) => {
console.log("responseJSON", responseJson);
dispatch(getLoginAttempt(false))
dispatch(getLoginAttemptFailure(responseJson.message))
})
}
})
.catch((error) => {
console.log("error", error);
dispatch(getLoginAttempt(false))
dispatch(getLoginAttemptFailure(error))
})
}}
const getLoginAttempt = (isLoading) => {
return {
type: FETCH_LOGIN,
isLoading: isLoading
};
}
const getLoginAttemptSuccess = (responseJson) => {
return {
type: FETCH_LOGIN_SUCCESSFUL,
responseJson: responseJson
};
}
const getLoginAttemptFailure = (error) => {
return {
type: FETCH_LOGIN_FAILURE,
error: error
};
}
Example Reducer(Login Reducer)
const initialState = {
loginResponseData: {},
isLoggedIn: false,
isLoginLoading: false,
loginError: null
}
const loginReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_LOGIN:
return Object.assign({}, state,
{
isLoginLoading: true,
isLoggedIn: false
})
case FETCH_LOGIN_SUCCESSFUL:
return Object.assign({}, state,
{
isLoginLoading: false,
isLoggedIn: true,
loginResponseData: action.responseJson
})
case FETCH_LOGIN_FAILURE:
return Object.assign({}, state,
{
isLoginLoading: false,
isLoggedIn: false,
loginError: action.error
})
default:
return state
}
}
then connected the reducer with a root reducer in redux store configuration and connected to a screen(Login Screen) as below
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);

I have created a library for handling navigation problems like this one.
React Navigation Helpers
Its setup and usage are very basic. You simply need to set your navigation reference into the top global level (which the library handles it itself). You simply put this code segment into your navigation logic and set global level navigator with your main navigator.
import NavigationService from "react-navigation-helpers";
<MainNavigator
ref={navigatorRef =>
NavigationService.setGlobalLevelNavigator(navigatorRef)
}
/>
After you're done with the setup. You can navigate to anywhere to anywhere with this library.
USAGE
// Goes to spesific screen from prop
NavigationService.navigate("home")
// Goes to preview screen
NavigationService.back()
I believe that this solution will solve your confusion and fix your problem for the whole project.
Simply you need to put this line of code into your successful fetch result:
NavigationService.navigate("home")
If you have trouble using react-navigation-helpers, ask me anything 😇

Related

React Hook does not set on first API call

So I am sure I am messing something up, but I am not super skilled at API.
So I am trying to make an API call to check if the user exists, if user exists then move about business, if not then do other stuff.
So my first call gets the data, and the user DOES exist, the hook is setting to true, however in my log it fails and the next API is ran. However if I do it a 2nd time, it is true...
What am I doing wrong.
const handleSubmit = async () => {
const data = await axios
.get(`URL`, {
})
.then((resp) => {
if (resp.data.user.name) {
setCheckUser(true);
console.log(resp.data.user.name);
}
return data;
})
.catch((err) => {
// Handle Error Here
console.error(err);
});
console.log(checkUser);
if (!checkUser) {
console.log('No User Found');
//Do Stuff//
}
};
I think the problem here is that setCheckUser(true) is an async operation, so there is no guarantee that the checkUser variable will turn to true right away.
Maybe you can solve this by using a useEffect block like this
//somewhere on the top of your file, below your useState statements
useEffect(()=> {
if (!checkUser) {
console.log('No User Found');
//Do Stuff//
}
}, [checkUser])
const handleSubmit = async () => {
const data = await axios
.get(`URL`, {
})
.then((resp) => {
if (resp.data.user.name) {
setCheckUser(true);
console.log(resp.data.user.name);
}
return data;
})
.catch((err) => {
// Handle Error Here
console.error(err);
});
};

React redux - passing parameters to url - error - Actions must be plain objects

I want to attach params to react redux fetch action and I searched for many days the redux docs, but even after trying out a few things i am getting this error:
[Unhandled promise rejection: Error: Actions must be plain objects. Use custom middleware for async actions.]
https://codesandbox.io/s/fast-framework-ct2fc?fontsize=14&hidenavigation=1&theme=dark
The original action looks like this:
export function fetchArticleDetails() {
return apiAction({
url: "http://myurl/appApi/2.0.0/getData/1", //1 should be an optional value
onSuccess: setArticleDetails,
onFailure: () => console.log("Error occured loading articles"),
label: FETCH_ARTICLE_DETAILS
});
}
function setArticleDetails(data) {
console.log(data);
return dispatch({
type: SET_ARTICLE_DETAILS,
payload: data
});
}
i tried to set the param directly
export function fetchArticleDetails(id)
...
url: `http://myurl/appApi/2.0.0/getData/${id}`,
or some variations to put the params in the payload directly
function setArticleDetails(data) {
console.log(data);
return dispatch({
type: SET_ARTICLE_DETAILS,
payload: data,
userid: id
});
}
All this results in the same error. Anyone have an idea where to place the dynamic data to solve it?
Another idea could be to set the params in my reducer maybe?
Update store/index.js
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import apiMiddleware from "../middleware/api";
const store = createStore(rootReducer, applyMiddleware(apiMiddleware));
window.store = store;
export default store;
update: middleware/api.js
import axios from "axios";
import { API } from "../actions/types";
import { accessDenied, apiError, apiStart, apiEnd } from "../actions/api";
const apiMiddleware = ({ dispatch }) => next => action => {
next(action);
if (action.type !== API) return;
const {
url,
method,
data,
accessToken,
onSuccess,
onFailure,
label,
headers
} = action.payload;
const dataOrParams = ["GET", "DELETE"].includes(method) ? "params" : "data";
// axios default configs
axios.defaults.baseURL = process.env.REACT_APP_BASE_URL || "";
axios.defaults.headers.common["Content-Type"] = "application/json";
axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
if (label) {
dispatch(apiStart(label));
}
axios
.request({
url,
method,
headers,
[dataOrParams]: data
})
.then(({ data }) => {
dispatch(onSuccess(data));
})
.catch(error => {
dispatch(apiError(error));
dispatch(onFailure(error));
if (error.response && error.response.status === 403) {
dispatch(accessDenied(window.location.pathname));
}
})
.finally(() => {
if (label) {
dispatch(apiEnd(label));
}
});
};
export default apiMiddleware;
function apiAction()
function apiAction({
url = "",
method = "GET",
data = null,
accessToken = null,
onSuccess = () => {},
onFailure = () => {},
label = "",
headersOverride = null
}) {
return {
type: API,
payload: {
url,
method,
data,
accessToken,
onSuccess,
onFailure,
label,
headersOverride
}
};
}
There are a couple of issues with the code. apiMiddleware should only pass the action to the next middleware in the chain if it's not of type API.
const apiMiddleware = ({ dispatch }) => (next) => (action) => {
if (action.type !== API) {
return next(action)
}
// do stuff
}
Since the apiMiddleware dispatches what onFailure returns, the function has to return an object. In fetchArticleDetails, you're passing () => console.log("Error occured loading articles") causing apiMiddleware to dispatch undefined.
export function fetchArticleDetails(id) {
return apiAction({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
onSuccess: setArticleDetails,
onFailure: (error) => ({
type: FETCH_ARTICLE_ERROR,
payload: error
}),
label: FETCH_ARTICLE_DETAILS
})
}
CodeSandbox
I would strongly recommend using React Query to simplify data fetching, managing, and syncing server state.

Pass data from FormulateForm to a mapped action using Vuex

I'm currently using Vue Formulate to pass data, using #submit="login" on a FormulateForm, to a login(data) function.
Everything's working nicely as long as I keep the logic inside the component and I can send the data to my server using axios.
Thing is, I would like to put this login function in my Vuex store, as an action, but when I refer the #submit="login" from FormulateForm to the ...mapActions(["login"]) function, there is no data passed inside.
I logged data in the login(data) action, and I get this:
Response from console.log(data) in the vuex module
I could bind values from my inputs into the store and get them from there, but I'd prefer to keep this simple and use #submit.
Is it at all possible to do so?
Overview of the actual code that is working:
methods: {
login(data) {
axios
.post("http://localhost:3000/api/auth/login", data, {
withCredentials: true
})
.then(res => {
if (res.status === 200) {
this.setUserRole(res.data.isAdmin);
this.$router.push("/");
}
})
.catch(err => {
if (err.response.status === 404) {
// TODO: gestion d'erreur
} else if (err.response.status === 401) {
// TODO: gestion d'erreur
}
});
}
)
<FormulateForm #submit="login">
Overview of what I want, that is not working:
methods: {
...mapActions(["login"])
)
<FormulateForm #submit="login">
Inside Vuex module user.js:
const actions = {
login: data => {
console.log(data);
axios
.post("http://localhost:3000/api/auth/login", data, { withCredentials: true })
.then(res => {
if (res.status === 200) {
this.setUserRole(res.data.isAdmin);
this.$router.push("/");
}
})
.catch(err => {
if (err.response.status === 404) {
// TODO: gestion d'erreur
} else if (err.response.status === 401) {
// TODO: gestion d'erreur
}
});
}
};
As stated, the console.log(data)does not return my FormulateForm values as it does currently.
You did not dispatch the action login.
Do this
<FormulateForm #submit="handleLogin">
methods: {
...mapActions(["login"]), // here, you have mapped `this.login()` to this.$store.dispatch('login')
handleLogin(data) {
this.login(data); // pass data as a parameter
}
)
Then your vuex user.js store should be changed to
const actions = {
login: ({commit, state}, data) => { // it takes two arguments here
console.log(data);
}
};
For more on actions, please check the Vuex documentation
Do these things and it should work.

React native axios call throws 403 but postman correctly outputs the data

I'm working on a RN app, which has redux in it. Now I can login with the help of jwt but when Im trying the to get the data from my other component its giving me 403 error. Please find below the relevant code.
Here is my reducer:
const initState = {
isLoadingCollegeDashList : false,
collegeDashList:{},
collegeDashListFail:false
}
const collegeReducer = ( state = initState, action) => {
switch(action.type){
case 'IS_LOADING_COLLEGE_DASH_LIST' :
return{
...state,
isLoadingCollegeDashList: true,
collegeDashList : false
}
case 'COLLEGE_DASH_LIST' :
return {
...state,
isLoadingCollegeDashList : false,
collegeDashList : true,
userData : action.userData
}
case 'COLLEGE_DASH_LIST_FAIL' :
return{
...state,
isLoadingCollegeDashList:false,
collegeDashList: false,
collegeDashListFail: action.error
}
default :
return state
}
}
and here's my action that's making get request
export const populateCollege = (token) => {
const headers = {
'api-secret' : ...secret...,
'authorization':...authToken...,
'Content-Type': 'application/json',
}
return dispatch => {
dispatch(isLoadingCollegeDashList(true));
return axios.get( '...api/api/...', {
},{
headers:headers,
})
.then((response) => {
if(response.status < 300){
dispatch(isLoadingCollegeDashList(false))
dispatch(collegeDashList(response))
console.log(response);
}
else{
response.json().then((responseJSON) => {
console.log("responseJSON",responseJSON);
dispatch(isLoadingCollegeDashList(false))
dispatch(collegeDashListFail(responseJSON.message))
})
}
})
.catch((error) => {
console.log("error",error);
dispatch(isLoadingCollegeDashList(false))
dispatch(collegeDashListFail(error))
})
}
}
export const isLoadingCollegeDashList = (bool) => {
return{
type:'IS_LOADING_COLLEGE_DASH_LIST',
isLoadingCollegeDashList:bool
}
}
export const collegeDashList = (userData) => {
return{
type:'COLLEGE_DASH_LIST',
userData
}
}
export const collegeDashListFail = (error) => {
return{
type:'COLLEGE_DASH_LIST_FAIL',
error
}
}
here's action that im calling if you want to check it
const mapDispatchToProps = dispatch => ({
populateCollege : (token) => dispatch(actions.populateCollege({token}))
});
PS I've for now stored token in the state of one hence passing the token from this dispatch itself.
Let me know if you need any clarification / more information then do let me know. Thanks in advance
Make sure you have the authorisation schema before your token. The schema can be like Basic, Bearer or any other value based on your authorisation details. (eg. Authorization: Bearer TOKEN).
Also, try to reuse your auth headers while creating the axios instance so you won't need to inject them on every call.

NuxtJs - Cannot read property 'headers' of undefined

I'm a newbie in NuxtJs. I'm trying to implement an external API Call with axios which I get token and store it on cookie. Everything works well in development. But when I try to run npm run generate it gives me errors that I don't know what to do.
When I delete nuxtSeverInit, npm run generate runs smoothly. And after some research, i think that nuxtServerInit that I'm using shouldn't be used. Can anyone please tell me how to make it work.
This is the first project in a new company, so I'm trying to prove myself. Please help me with it. Will you.
Click here for image that shows the error that appears after npm run generate
This is store/index.js file
import Vuex from 'vuex'
var cookieparser = require('cookieparser')
const createStore = () => {
return new Vuex.Store({
state: {
auth: null,
},
mutations: {
update (state, data) {
state.auth = data
}
},
actions: {
nuxtServerInit ({ commit }, { req }) {
let accessToken = null
if (req.headers.cookie) {
var parsed = cookieparser.parse(req.headers.cookie)
if(parsed){
accessToken = parsed.auth
}
}
commit('update', accessToken)
},
}
})
}
export default createStore
middleware/authenticated.js file
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.auth) {
return redirect('/login')
}
}
middleware/notAuthenticated.js file
export default function ({ store, redirect }) {
// If the user is authenticated redirect to home page
if (store.state.auth) {
return redirect('/app/dashboard')
}
}
login.vue file
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
this.button_title = 'One moment ...';
let submitted_user_data = {
'username': this.emailAddress,
'client_id': this.user_uuid,
'password': this.password,
}
MerchantServices.do_user_login(submitted_user_data)
.then(response => {
let access_token = response.data.access_token;
this.postLogin(access_token);
})
.catch(error => {
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
});
return;
}
});
},
postLogin: function(access_token_val) {
if(access_token_val != ''){
setTimeout(() => {
const auth = {
accessToken: access_token_val
}
this.$store.commit('update', auth)
Cookie.set('auth', auth)
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
this.$router.push('/app/dashboard')
}, 1000)
}else{
alert('hello')
}
},
and the last user login api call which also returns the token.
do_user_login(user){
var user_details = 'username='+user.username+'&client_id='+ user.client_id +'&grant_type=password&password='+user.password+''
return axios.post('myapiurl', user_details )
.then(response => {
return response;
});
},
Acording to Nuxt Docs req is not available on nuxt generate.
You should use nuxt build and than nuxt start after that.