React Native state getting empty after action REDUX_STORAGE_SAVE - reactjs

I am new in react native and I am kind of stuck in redux. I am trying to fetch the vehicles from the api endpoint, I am using redux-saga, the request sent, successfully fetches the data and dispathes the FETCH_VEHICLE_SUCCESS action. and state gets updated in redux store. But after that an action(REDUX_STORAGE_SAVE as shown in the image below) is automatically triggered and it empties the state i.e vehicles(refer to the image below). Why is it happening?
I am using redux-logger for logging, as shown on the picture below the vehicles object gets empty.
saga.js
function fetchVehicleCall() {
console.log('here');
return new Promise((resolve, reject) => {
RegisterHelper.fetchAllVehicles()
.then(res => {
console.log('now');
resolve(res);
})
.catch(err => reject(err));
});
}
function* fetchVehicleRequest() {
while (true) {
yield take(GET_VEHICLE_REQUEST);
try {
const response = yield call(fetchVehicleCall);
yield put(fetchVehicleSuccess(response));
console.log('SAGA FETCH SUCCESS: ', response);
} catch (err) {
console.log('SAGA FETCH ERR: ', err);
}
}
}
export default function* root() {
yield fork(fetchVehicleRequest);
}
actions.js
export function fetchVehicleSuccess(vehicles) {
return {
type: FETCH_VEHICLE_SUCCESS,
vehicles,
};
}
reducer.js
case FETCH_VEHICLE_SUCCESS:
return Object.assign({}, state, {
vehicles: action.vehicles,
});

Related

How to call another saga function from current saga function in same saga file?

I have 2 saga function in my saga handler file.
*
function* fetchCustomDashboardCards - fetched the cards & saves cards in store
function* addPanelToCustomDashboard - adds a new card to customDashboard.
now I am invoking addPanelToCustomDashboard to add a card to dashboard, in which i make a api call to add the card. what i want here is, if my addPanelToCustomDashboard return success i want to call first saga fetchCustomDashboardCards to load the dashboard with newly added card.
could someone tell me how would i call the another saga function from current saga function?
below is the code of both saga functions -
1st saga function -
export function* fetchCustomDashboardCards(action) {
const { botId } = action?.data;
const fetchCardsUrl = urlConfig.fetchCustomDashboardCardsUrl(botId);
try {
const { status, data } = yield call(() => api.get(fetchCardsUrl));
if (status === 200) {
//dispatch action to save cards of custom dashboard
} else {
//dispatch action saying some error occured while fetching cards
//remove this when apis start working
yield put(customDashboarCardsFetched({ cards: data?.cards }));
}
} catch (error) {
console.log('error occured while fetching custom dashboard cards');
//remove this when apis start working
yield put(customDashboarCardsFetched({ cards: [] }));
}
}
2nd saga function -
export function* addPanelToCustomDashboard(action) {
const { botId } = action?.data;
const addPanelUrl = urlConfig.addPanelToCustomDashboard(botId);
try {
const { status, data } = yield call(() =>
api.post(addPanelUrl, {
panelInfo: action?.data?.panelInfo
})
);
if (status === 200) {
***//ON SUCCESS RELOAD DASHBOARD WITH NEWLY ADDED CARD BY CALLING 1st SAGA FUNCTION***
} else {
//dispatch action saying some error occured
//remove this when apis start working
yield put(panelAddedToCustomDashboard({ panel: action?.data?.panelInfo }));
}
} catch (error) {
console.log('error occured while adding panel to custom dashboard');
//remove this when apis start working
yield put(panelAddedToCustomDashboard({ panel: action?.data?.panelInfo }));
}
}

await of generator completing in redux-saga

I have code in component,
I need to get updated authorizedError value in function, but i get old value authorized error
// login component
const authorizedError = useSelector((state: RootState) => state.user.authorizedError);
const onSignInPress = useCallback(async () => {
await dispatch(userActions.postLoginUser({username: email, password}));
if (authorizedError) {
setNotificationErrors(['Wrong login or password'])
showNotification();
}
}, [authorizedError, validate, email, password]);
// postLoginUserSaga.js
export default function* postLoginUserSaga({
payload,
}: PayloadAction<UserCredentialsPayload>) {
try {
yield put(setSignInError(false));
const {
data: {
payload: { access_token },
status,
},
} = yield transport.post(URLS.postLoginUserURL, payload);
if (status !== "Ok") {
throw new Error(status);
}
yield setItemAsync(ACCESS_TOKEN_KEY, access_token);
yield put(setSignIn(true));
} catch (error) {
console.error("User login failed", error);
yield put(setSignInError(true));
}
}
// sagaRoot file
export default function* userRootSaga() {
yield all([
checkAuthSaga(),
takeEvery(actions.postLoginUser, postLoginUserSaga),
takeEvery(actions.postRegistrationUser, postRegistrationUserSaga),
takeEvery(actions.getProfileData, getProfileDataSaga),
]);
}
Redux actions don't return a promise, you can't use them like this.
If you want to use the promise API you can use the redux-thunk middleware which supports it.
If you want to use sagas you can add a callback action property instead.
// in component callback
dispatch(userActions.postLoginUser({username: email, password, callback: (authorizedError) => {
if (authorizedError) {
setNotificationErrors(['Wrong login or password'])
showNotification();
}
}));
// in saga
try {
...
action.callback();
} catch (err) {
action.callback(err);
}
Although that has its own issues.
Usually you communicate from sagas back to components by changing the redux state, so you can e.g. have a state for redux error, and based on that field show the error message or show different component if the login was succesful.

React and Redux toolkit - reject after promise

I'm working on a React Native app. I have a signup screen which has a button, onclick:
const handleClick = (country: string, number: string): void => {
dispatch(registerUser({ country, number }))
.then(function (response) {
console.log("here", response);
navigation.navigate(AuthRoutes.Confirm);
})
.catch(function (e) {
console.log('rejected', e);
});
};
The registerUser function:
export const registerUser = createAsyncThunk(
'user/register',
async ({ country, number }: loginDataType, { rejectWithValue }) => {
try {
const response = await bdzApi.post('/register', { country, number });
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.message);
}
},
);
I have one of my extraReducers that is indeed called, proving that it's effectively rejected.
.addCase(registerUser.rejected, (state, {meta,payload,error }) => {
state.loginState = 'denied';
console.log(`nope : ${JSON.stringify(payload)}`);
})
But the signup component gets processed normally, logging "here" and navigating to the Confirm screen. Why is that?
A thunk created with createAsyncThunk will always resolve but if you want to catch it in the function that dispatches the thunk you have to use unwrapResults.
The thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate.
The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an unwrapResult function that can be used to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:
import { unwrapResult } from '#reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then(originalPromiseResult => {})
.catch(rejectedValueOrSerializedError => {})
}

How to make await work with redux Saga in React?

The await does not seem to work with Redux saga. I need to wait for my API call to finish and then execute the remaining code. What happens now is that AFTER CALL gets printed before the RESPONSE which means await does not seem to work at all. I'm using async calls but not sure what needs to be done extra from the redux saga side?
async componentWillMount() {
console.log("BEFORE CALL")
await this.props.getUserCredit()
console.log("AFTER CALL")
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(MyComponent);
Action
export const getUserCredit = () => {
return {
type: GET_USER_CREDIT,
};
};
Redux Saga
const getUserCreditRequest = async () => {
const response = await Api.get(getCreditUrl)
console.log("REPONSE!!!")
console.log(response)
return response
}
function* getUserCredits() {
try {
const response = yield call(getUserCreditRequest);
if (response.status === okStatus) {
yield put({
userCredit: response.data.userCredit
}
));
}
} catch (error) {}
}
export function* getUserCredit() {
yield takeLatest(GET_USER_CREDIT, getUserCredits);
}
export default function* rootSaga() {
yield all([fork(getUserCredit)]);
}
Normally, init / fetching takes place during componentDidMount and don't use async or await inside components. Let the saga middleware do its thing via yield.
// In your component
componentDidMount() { // not async
this.props.getUserCredit(); // dispatch `GET_USER_CREDIT` action
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(YourComponent);
You shouldn't be using async/await pattern. As redux-saga handles it by the yield keyword. By the time call is resolved you will have the value available in response.
in actions.js, you should have an action that will carry your data to your reducer:
export function getUserCredits(userCredit) {
return {
type: types.GET_USER_CREDIT_SUCCESS,
payload: userCredit
};
}
Your saga should handle the API call like so:
function* getUserCredits() {
try {
const response = yield axios.get(getCreditUrl); <--- This should work
// No need for if here, the saga will catch an error if the previous call failed
yield put(actions.getUserCredits(response.data.userCredit));
} catch (error) {
console.log(error);
}
}
EDIT: example of using axios with redux-saga

Why fetchIntercept.register hits only once on button click in redux-saga pattern?

I am using fetch-intercept to intercept all the http-requests in a react application. I added a loader in the app component and show/hide loader on the basis of request/response recieved. Its working fine but when I click the button second time to call the fetch inside saga file, it is not hitting request method inside fetch interceptor.
interceptor.js
import fetchIntercept from 'fetch-intercept';
import createstore from './store';
const unregister = fetchIntercept.register({
request: function (url, config) {
// Modify the url or config here
let withDefaults = Object.assign({}, config);
withDefaults.headers = withDefaults.headers || new Headers({
'AUTHORIZATION': `Bearer shikhabwehfbwefbffbweufwfwf`
});
// call to action to show spinner
createstore.dispatch({
type: 'SET_LOADER',
loading: true
});
return [url, withDefaults];
},
requestError: function (error) {
createstore.dispatch({
type: 'SET_LOADER',
loading: false,
error: error
});
// Called when an error occured during another 'request' interceptor call
return Promise.reject(error);
},
response: function (response) {
// Modify the reponse object
createstore.dispatch({
type: 'SET_LOADER',
loading: false
});
return response;
},
responseError: function (error) {
createstore.dispatch({
type: 'SET_LOADER',
loading: false,
error: error
});
// Handle an fetch error
return Promise.reject(error);
}
});
export default unregister;
Saga.js
export function* fetchNews() {
try {
const json = yield fetch('https://randomuser.me/api/?results=10').then(results => {
return results.json();
});
unregister();
yield put({
type: "NEWS_RECEIVED",
json: json.results
});
} catch (error) {
yield put({type: "NEWS_RECIEVED_ERROR", error});
}
}
// watcher
export function* getNewsActionWatcher() {
yield takeLatest('GET_NEWS', fetchNews); // action , worker
}
export default function* rootSaga() {
yield all([getNewsActionWatcher()]);
}
Component.ts
import React from 'react';
import { connect } from 'react-redux'
import { getNews } from '../../actions';
const ButtonComponent = ({ getNews }) => (
<button onClick={getNews}>Get News</button>
);
const mapDispatchToProps = {
getNews: getNews
};
const GetButton = connect(null, mapDispatchToProps)(ButtonComponent);
export default GetButton;
Could anyone please help me out with this issue ?
You need to use dispatch in mapDispatchToProps to dispatch the GET_NEWS action.
const mapDispatchToProps = (dispatch) => {
getNews: () => dispatch(getNews())
};
Another nitpick is that takeLatest in getNewsActionWatcher will only run one forked task at a time. Killing the previous task once another GET_NEWS action is received.
So using all effect in rootSaga() is not useful.

Resources