React Redux rejectWithValue() not working - react-native

I'm currently facing a problem, when I try to reject a value when my node-fetch request fail, thunkApi.rejectWithValue() isn't working. However when my request is pending or when It's fulfilled, It's working fine.
Here's my slice :
export const userSlice = createSlice({
name: "user",
initialState: initialState as User,
reducers: {
...
},
extraReducers: (builder) => {
...
builder.addCase(changePassUser.pending, (state) => {
GGLog("FETCHING CHANGEPASS API...");
state.isFetching = true;
return state;
});
builder.addCase(changePassUser.fulfilled, (state, { payload }) => {
GGLog("FULFILLED CHANGEPASS:", JSON.stringify(payload));
state.isFetching = false;
state.isSuccess = true;
state.isError = false;
return state;
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
builder.addCase(changePassUser.rejected, (state, { payload }: any) => {
GGLog("REJECTED CHANGEPASS:", JSON.parse(payload));
state.isFetching = false;
state.isError = true;
state.errorMessage = payload.data;
return state;
});
},
});
Here's my thunk :
export const changePassUser = createAsyncThunk(
"users/password/update",
async ({ oldpassword, newpassword }: RegisterParameters, thunkAPI) => {
try {
const res = await changePassApi.changePass.return({
oldpassword: oldpassword,
newpassword: newpassword,
});
GGLog("API_CHANGEPASS_RES:", res);
const data = await res.json();
if (res.ok) {
GGLog("API_DATA_RESPONSE_OK: ", data);
const tokenData = JSON.stringify(res.headers);
const token = JSON.parse(tokenData).map["x-auth"];
await localStorage.store("token", token);
return data;
} else {
GGLog("API_DATA_RESPONSE_NOK: ", data);
return thunkAPI.rejectWithValue(data);
}
} catch (e) {
GGLog("Error while fetching Login API => ", e);
return thunkAPI.rejectWithValue(e);
}
}
);
And here's the result in the console :
Console output
Any ideas ? Am I missing something ?
Thanks :)

Okay I've found my problem, I was just focused on the thunk and didn't pay attention to the promise rejection. I was trying to parse a JSON that does'nt exist... Just remove the GGLog("REJECTED CHANGEPASS:", JSON.parse(payload));in the slice.
It's working fine now !

Related

change toolkit redux state inside of fetch

I just want to update state outside of component after server give me failed response.
the project is react native .
After a lot of online search on website like this , I learn that i need to import my store and use dispatch method. but when i import storage and dispatch it, it give me this error: Property 'crypto' doesn't exist .
this is my feachService.js code:
import { URL } from 'react-native-url-polyfill';
import { store } from '../store/index'
import { errorSliceActions } from '../store/slices/global/errorSlice'
export default (url, isGet) => {
let headers = {
Referer: new URL(url).origin,
}
headers['X-Requested-With'] = 'XMLHttpRequest'
return fetch(
url,
{
method: (isGet ? 'GET' : 'POST'),
headers: headers
})
.then(function (res) {
return res.json();
})
.then(function (res)
{
if(res && res.isSuccess == false && res.message) {
store.dispatch(errorSliceActions.add(res.message));
}
return {json: function() { return res; }};
})
.catch(function (error) { console.log(error.message); })
};
this is my storage js file:
import { configureStore } from '#reduxjs/toolkit';
import errorSliceReducer from './slices/global/errorSlice';
export const store = configureStore({
reducer: {
errorSlice: errorSliceReducer
}
});
this is my error slice.js file:
import { createSlice } from "#reduxjs/toolkit"
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const errorSlice = createSlice({
name: "errorSlice",
initialState: {
errors: [
]
},
reducers:
{
add: (state, action) => {
const newItem = {
id: uuidv4(),
message: action.payload
};
return [...state, newItem];
},
remove: (state, action) => {
return { errors: state.errors.filter(function(item) { return item.id != action.payload; }) }
}
}
})
export const errorSliceActions = errorSlice.actions;
export default errorSlice.reducer
and this is my code for executing fetch request
async function loadInfo (inputSearch, inputPage) {
if(config.dataurl) {
console.log(inputSearch);
setIsLoading(true);
const hasQuestionMark = config.dataurl.indexOf('?') > 0;
let targetUrl = '/test/test' + config.dataurl + (!hasQuestionMark ? '?' : '&');
targetUrl += 'page=' + inputPage;
targetUrl += '&search=' + inputSearch;
console.log(targetUrl);
const response = await feachService(appJSon.baseUrl + targetUrl , true);
const responseData = await response.json();
setIsLoading(false);
if(responseData.pagination)
setPagination(responseData.pagination);
if(!responseData.results)
setItems([]);
else
setItems(responseData.results);
}
}
useEffect(() => {
loadInfo('', 1).catch(function (error) { console.log(error.message); });
}, [])
what am i doing wrong ?

How to use redux on a global functions

Iam currently working with a react-native project and wanted to use redux now what i want is that a global function that handles all firebase fetch data here is my globla function
connectFunctions.js
export const pullDataFromFirebaseSubCollection = async (collectionName, docId, subCollection, reducerName,) => {
const q = query(collection(db, collectionName,docId,subCollection));
const querySnapshot = await getDocs(q);
const documents = querySnapshot.docs.map((doc) => ({ key_id: doc.id, ...doc.data() }));
var redData = {
reducerName:reducerName,
data:documents
}
store.dispatch(middlewareDispatch(redData))//tried it inside the function does not work
return documents;
}
so on my App.js i imported then connectFunction.js and use pullDataFromFirebaseSubCollection
useEffect(()=>{
let isMounted = true; // note mutable flag
onAuthStateChanged(auth, (user) => {
if (user) {
console.log("user appjs", user);
dispatch(updateLoginStatus(true));
pullDataFromFirebaseSubCollection("Students","1","playlist","playlist").then((data)=>{
}).catch((err)=>{
console.log("logged error", err);
})
} else {
dispatch(updateLoginStatus(false))
}
_handleFinishLoading()
})
return () => { isMounted = false }
},[])
so on my library.js
i called store.getState().reducer.playlist the result is empty

react native fetch hook and refresh jwt token

i have made a custom hook to fetch data from api, but the token only valid for 5 second.
so i made this hook
the problem is when i call the hooks from my page it called many time and the refresh token already expired
when i access the api i will check the response first if the token invalid i tried to refresh my token using handleRefreshToken
nb : im using useContext for my state management
import React, {useEffect, useState, useContext} from 'react';
import {View, StyleSheet} from 'react-native';
import {AuthContext} from '../Auth/Context';
import AsyncStorage from '#react-native-community/async-storage';
import {urlLogin, URLREFRESHTOKEN} from '../Configs/GlobaUrl';
const FetchData = () => {
const {loginState, authContext} = useContext(AuthContext);
const [data, setData] = useState([]);
const [message, setMessage] = useState('');
const [loading, setIsLoading] = useState(false);
const {dispatchRefreshToken} = authContext;
const handleRefreshToken = async (callbackUrl, callbackBody) => {
const refBody = {
client_id: loginState.ipAddress,
ipAddress: loginState.ipAddress,
employee_id: loginState.userData.Pegawai_Id,
jwttoken: loginState.userToken,
refresh_tokenn: loginState.refreshToken,
};
console.log('======refreshtokencalled==========');
console.log(refBody.refresh_tokenn, '<=refresh token');
console.log(refBody.jwttoken, '<=jwt token');
let response = await fetch(URLREFRESHTOKEN, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(refBody),
redirect: 'follow',
});
let result = await response.json();
console.log(result, ' ini result');
if (
result.item3 !== 'refresh token gagal' &&
result.item3 !== 'refresh token sudah tidak berlaku'
) {
let refresh = result.item2;
let token = result.item1;
// the backend doesnt send any succes / error code only item1 for token, //item2 refresh token and item3 for error
dispatchRefreshToken(token, refresh);
await AsyncStorage.setItem('refreshToken', refresh);
await AsyncStorage.setItem('token', token);
return getData(callbackUrl, callbackBody);
} else {
return null;
}
};
const getData = async (url, body) => {
setIsLoading(true);
let result;
try {
let response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${loginState.userToken}`,
},
body: JSON.stringify(body),
redirect: 'follow',
});
if (response.status == '401') {
let refreshResult = await handleRefreshToken(url, body);
console.log(refreshResult);
} else {
result = await response.json();
console.log(result);
console.log(loginState.refreshToken);
if (result.code == '1') {
setData(result.data);
setIsLoading(false);
} else {
throw result;
}
}
} catch (err) {
setData([]);
console.log(err, 'masuk error usefetchbybutton');
console.log(err.message, err.code);
setIsLoading(false);
setMessage(err);
}
};
return {
data: data,
message: message,
loading: loading,
getData: getData,
};
};
export default FetchData;
this is my dispatch refresh token
const authContext = useMemo(
() => ({
logIn: async (token, userData, refreshToken) => {
console.log(token, '<>', refreshToken, 'ini memoisa');
dispatch({
type: 'LOGIN',
token: token,
userData: userData,
refreshToken: refreshToken,
});
},
logOut: () => {
AsyncStorage.clear((error) => {
console.log(error);
});
dispatch({type: 'LOGOUT'});
},
dispatchRefreshToken: (userToken, refreshToken) => {
console.log(refreshToken, '=refresh dispatch=');
console.log(userToken, '=userToken dispatch=');
dispatch({
type: 'REFRESHTOKEN',
userToken: userToken,
refreshToken: refreshToken,
});
},
}),
[],
);
my reducer function
const loginReducer = (prevState, action) => {
switch (action.type) {
some case ...
case 'REFRESHTOKEN':
return {
...prevState,
userToken: action.userToken,
refreshToken: action.refreshToken,
};
}
};
Use recursion. The pseudo code is as follows
const getData = async (args, times) => {
// try to fetch data
const data = await Api.fetch(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return getData(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
The logical above can be encapsulated to all your api. Like this
const wrapApi = (api) => {
const wrappedApi = async (args, times) => {
const data = await api(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return wrappedApi(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
return wrappedApi;
}

Using AsyncStorage to show screen on first login

I'm trying to only show the disclosure screen the first time the user logs in by using AsyncStorage. Currently getData is returning a Promise and it goes straight to the landing screen on first login.
Could I get some help to get this functioning the way I want it to?
This is my login handler:
const key = 'key';
const storeData = async () => {
try {
await AsyncStorage.setItem('key', 'true');
} catch (error) {
// saving failed
console.log(error.message);
}
};
const getData = async key => {
let value = '';
try {
value = await AsyncStorage.getItem(key);
return JSON.parse(value);
} catch (e) {
console.log(error.message);
}
};
const _loginHandler = () => {
if (userName == '' || password == '') {
console.log('gagagagagagagagagagagagagagagagag');
} else {
setShowLoading(true);
const payload = {userName: userName.trim(), password: password.trim()};
setUserName('');
setPassword('');
_UserLoginHandler(payload).then(data => {
setShowLoading(false);
if (data.error) {
GlobalShowSnackBar(data.error);
} else {
setTimeout(() => {
setUserId(data);
//props.navigation.replace(getData("key")?'Landing':'Disclosure')
console.log('Key Value ' + JSON.stringify(getData('key'))); <-- this outputs Key Value {"_U":0,"_V":0,"_W":null,"_X":null}
if (getData('key')) {
props.navigation.navigate('Landing');
} else {
storeData(key);
props.navigation.navigate('Disclosure');
}
}, 500);
}
});
}
};
I got it to work with
getData('key').then(val => {
if (val) {
props.navigation.navigate('Landing');
} else {
storeData(key);
props.navigation.navigate('Disclosure');
}
});

Cant get Auth token passed into reducer in redux

I have my userKey in the 'userLogin' function below, but when i get ready to send the userkey into my reducer, its not there.
I've tried updating my action to wrapping up the setUserId function into 'userLogin', but i keep getting null in my reducer (the actions are triggering, but its not making its way to the reducer. Help please!
action file
export const userLogin = userKey => {
return async dispatch => {
await AsyncStorage.setItem('userKey', userKey).then(userKey => {
console.log('userKey')
console.log(userKey) // have the key here
dispatch(setUserId(userKey))
})
}
}
export const setUserId = userKey => {
console.log('userKey')
console.log(userKey) // comes up as Null
return {
type: SET_USERID,
userKey: userKey
}
}
reducer file
import { SET_USERID, REMOVE_USERID } from '../actions/actionTypes'
const initialState = {
userKey: ''
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_USERID:
return {
...state,
userKey: action.userKey
};
case REMOVE_USERID:
return {
...state,
userKey: null
};
default:
return state;
}
};
export default reducer;
try this:
export const userLogin = userKey => {
return async dispatch => {
try {
await AsyncStorage.setItem('userKey', userKey)
console.log('userKey')
console.log(userKey) // have the key here
dispatch(setUserId(userKey))
} catch (ex) {
console.log(ex);
}
}
}