I want to fetch api data with redux-saga .
Here is how I call my api :
export const getData = () => {
agent
.get(
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=XXX"
)
.then((res) => {
console.log(res.body.data)
})
.catch((err) => {
console.log(err);
});
};
Here is my redux-saga :
import { takeLatest, put } from "redux-saga/effects";
import { delay } from "redux-saga/effects";
function* loadDataAsync() {
yield delay(500);
yield put({ type: "LOAD_DATA_ASYNC" });
}
export function* watchloadData() {
console.log("Im working buddy");
yield takeLatest("LOAD_DATA", loadDataAsync);
}
The problem Is I don't really know how to fetch data through redux-saga,I tried to do research but none of the information seemed to satisfy me.
Could you please give me some suggestions?
The problem Is I don't really know how to fetch data through redux-saga,
No, the problem is you don't really know function generators in javascript.
Anyways if I were to architect that piece of code I would do something like this:
export function* getData(){
yield agent
.get(
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=XXX"
)
};
And then:
function* loadDataAsync() {
try {
const { body: { data } } = yield getData();
console.log(data);
yield put({ type: "LOAD_DATA_ASYNC_SUCCESS" });
} catch(err) {
console.log(err);
yield put({ type: "LOAD_DATA_ASYNC_FAILED" });
}
}
Related
I' trying to filter my redux-saga generator functions api response like this.
function* loadSingleDataAsync(id) {
console.log('Second Saga Works');
let wholeData = [];
let singleData = [];
agent
.get(
`https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=xxx`
)
.then((res) => {
wholeData = [...res.body.data];
singleData = wholeData.filter((currencie) => currencie.id === id);
})
.catch((err) => {
console.log('--------err', err);
});
yield delay(700);
yield put({ type: 'RECEIVE_SINGLE_API_DATA_ASYNC', singleData });
}
After this I'm calling it this way:
export function* rootSaga() {
yield all([takeEvery('RECEIVE_API_DATA', loadDataAsync), takeEvery('RECEIVE_SINGLE_API_DATA', loadSingleDataAsync)]);
}
The first function is working but the second don't,Any suggestions why?
Adding a try, catch inside the saga helps you to know if the saga chain is broken because of any empty responses or data issues.
I am checking user is valid or not after displaying splash screen. So In my splash screen I checked the data from redux saga like below
Hide_Splash_Screen=()=>{
this.setState({
isVisible:false
})
if(this.props.phoneNumberData !== null )
{
this.props.navigation.navigate('HomeScreen')
}
else
{
this.props.navigation.navigate('PhoneVerification')
}
}
componentDidMount(){
setTimeout(()=>{
this.Hide_Splash_Screen();
}, 2000);
this.props.fetchSavePhoneNumber()
}
So if the user has install the app for firstime then PhoneVerification Screen is prompted and not it will directly go into HomeScreen
So here is my logic to check phone verification with otp and then store as a new user in saga
handleSendCode=()=>{
var phoneno = '+91'+this.state.phone
firebase
.auth()
.signInWithPhoneNumber(phoneno)
.then(confirmResult => {
this.setState({ confirmResult })
})
.catch(error => {
alert(error.message)
console.log(error)
})
this.setState({showotpScreen:true})
}
checkOtp=()=>{
this.state.confirmResult
.confirm(this.state.otp)
.then(user => {
this.props.NewPhoneNumber(user)
this.props.navigation.navigate('HomeScreen')
})
.catch(error => {
alert(error.message)
console.log(error)
})
}
For ReduxSaga implementation I take two different action and one reducer like this
phoneaction
export const fetchSavePhoneNumber = () => ({
type: 'FETCH_SAVE_PHONENUMBER',
});
export const NewPhoneNumber = (data) => ({
type:'SAVE_NEW_PHONENUMBER',
payload: data
});
phoneReducer
const initialState = {
phoneNumberData: null,
};
export default (state = initialState, { type, payload }) => {
switch(type){
case 'SAVE_NEW_PHONENUMBER':
return{
...state,
phoneNumberData: payload,
};
case 'IS_VALIDATING':
return{
...state,
};
default:
return state;
};
}
And my Two Saga will look like this
NewPhoneNumber
import { call, put, select, takeLatest } from 'redux-saga/effects';
import AsyncStorage from '#react-native-community/async-storage';
const phoneno = state => state.phone.phoneNumberData ;
function* PhoneVerifyTask(action){
const phoneNumberData = yield select(phoneno);
try{
yield call(AsyncStorage.setItem,'phoneVerify',JSON.stringify(phoneNumberData));
yield put({ type: 'SAVE_NEW_PHONENUMBER', payload: phoneNumberData });
}
catch(error){
console.log(error)
}
}
function* NewPhoneNumber(){
yield takeLatest('SAVE_NEW_PHONENUMBER',PhoneVerifyTask)
}
export default NewPhoneNumber;
FetchSavePhoneNumber
import { call, put, takeLatest } from 'redux-saga/effects';
import AsyncStorage from '#react-native-community/async-storage';
function* fetchVerifyPhoneNumber(action){
yield put({
type: 'IS_VALIDATING',
});
try
{
const response = yield call(AsyncStorage.getItem,'phoneVerify')
yield put({
type: 'SAVE_NEW_PHONENUMBER',
payload: JSON.parse(response) || null
});
}
catch(error){
console.log(e);
yield put({
type: 'SAVE_NEW_PHONENUMBER',
payload: {
phoneNumberData: null
},
});
}
}
function* FetchSavePhoneNumber(){
yield takeLatest('FETCH_SAVE_PHONENUMBER',fetchVerifyPhoneNumber)
}
export default FetchSavePhoneNumber;
But after successfully storing newPhoneNumberdata and fetchingExisting PhoneNumberdata my whole redux act as a infinite loop
This is probably because your component that calls FETCH_... is re-rendered and calls fetch another time.
I'd suggest either calling the action higher up in your app (in your App.js constructor maybe). You can also fire sagas once on app load in your generator directly.
I'm trying to fetch data from API In my redux-saga but I'm getting following error from here const {body: {data}} =yield getData() in my redux-saga:
cannot read property body of udefined
Here is my API Function:
export function* getData() {
yield agent
.get(
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=xxx"
)
.then((res) => {
getCurrencies(res.body.data);
console.log('--------res.body.data', res.body.data);
setPageCount();
})
.catch((err) => {
console.log(err);
});
}
.then is returning the data itself,So it is not undefined
Here is my redux-saga itself:
function* loadDataAsync() {
console.log("SAGAS WORKS");
yield delay(5000);
try {
const {body: {data}} =yield getData()
console.log(data)
yield put({type:"LOAD_DATA_ASYNC_SUCCESS"});
} catch (err) {
console.log(err)
yield put({type:"LOAD_DATA_ASYNC_ERROR"})
}
}
export function* watchLoadDataAsync() {
yield takeLatest("LOAD_DATA_ASYNC", loadDataAsync);
}
Any solutions please?
You need to use call to get the data back from your asynchronous action
function* loadDataAsync() {
console.log("SAGAS WORKS");
yield delay(5000);
try {
const {body: {}} = yield call(agent.get, "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=xxx");
console.log(body.data);
// Call your other functions here
getCurrencies(body.data);
setPageCount();
yield put({type:"LOAD_DATA_ASYNC_SUCCESS"});
} catch (err) {
console.log(err)
yield put({type:"LOAD_DATA_ASYNC_ERROR"})
}
}
For more information check out the official docs for call
How to stop saga being called multiple times. Once i dispatched an action i received the result multiple times. i don't know what i'm missing here.
Saga.js
export function* watchRegisterUser() {
yield takeLatest(REGISTER_USER, registerWithEmailPassword);
}
export function* watchLoginUser() {
const logi = yield takeLatest(LOGIN_USER, loginWithEmailPassword);
}
export function* watchLogoutUser() {
yield takeLatest(LOGOUT_USER, logout);
}
export default function* rootSaga() {
yield all([
fork(watchLoginUser),
fork(watchLogoutUser),
fork(watchRegisterUser)
]);
}
When i put wrong credentials i get the response and notification is displayed, working fine for the first time. But when i change the credentials, i get notifications (multiple times). Even when state
Reference: Same problem here
I'm not sure what am i missing here.
Thanks
Update
function* loginWithEmailPassword({ payload }) {
const { history } = payload;
try {
const response = yield call(loginWithEmailPasswordAsync, payload.user);
console.log("login :", response);
if (response.status >= 200 && response.status < 300) {
const loginUser = yield response.json();
localStorage.setItem("access_token", loginUser.access_token);
yield put(loginUserSuccess(loginUser));
history.push("/");
} else if (response.status === 400) {
yield put({
type: LOGIN_USER_FAILURE,
error: "Email and Password are wrong!"
});
}
} catch (error) {
yield put({ type: LOGIN_USER_FAILURE, error: "Something went wrong!" });
}
}
you could try debounce to get around this, in the example below I debounce repeated actions withing a 500ms window.
function* createAutocompleteLists(action) {
const state = yield select()
if (!state.users.loaded && !state.users.loading) {
yield put(getUsers())
yield take([GET_SUCCESS, GET_FAIL])
}
yield call(parseAutocomplete, state.couchdbSavedSearch.current_share_with, state)
}
function* createAutocompleteForwardLists(action) {
const state = yield select()
if (!state.users.loaded && !state.users.loading) {
yield put(getUsers())
yield take([GET_SUCCESS, GET_FAIL])
}
yield call(parseForwardAutocomplete, state.forward.current_forward_to, state)
}
function* debounceCurrentShareWith () {
yield debounce(500, [GET_SUCCESS, SET_CURRENT_SHARE_WITH, SPACE_SET_CURRENT_SHARE_WITH], createAutocompleteLists)
}
function* debounceCurrentShareWithForward () {
yield debounce(500, [GET_SUCCESS, FORWARD_SET_CURRENT_FORWARD_TO], createAutocompleteForwardLists)
}
export default function* usersSagas() {
yield spawn(debounceCurrentShareWith)
yield spawn(debounceCurrentShareWithForward)
}
Here is store/store.js
...
const initSagaMiddleware = createSagaMiddleware();
const middlewares = [initSagaMiddleware, fetchPhotosMiddleware, putPhotoMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
...
initSagaMiddleware.run(rootSaga);
export default store;
sagas/api-saga.js
export default function* rootSaga() {
yield all([
photoWatcher()
]);
}
function* photoWatcher() {
yield takeEvery(PUT_PHOTO, putPhotoWorker);
}
function* putPhotoWorker(action) {
try {
const payload = yield call(putPhoto, action.urlParams, action.body);
yield put({ type: PHOTO_UPDATED, payload });
} catch (err) {
yield put({ type: API_ERROR_PUT_PHOTO, payload: err });
}
}
and services/api.js
export function putPhoto(urlParams, body) {
return axios.put('/abc/' + urlParams.key + '/def/' + urlParams.id + '/edit', body)
.then(res => {
return res.data;
})
.catch(err => err);
}
My problem is: even when I get error from an api call, redux-saga putting PHOTO_UPDATED instead of API_ERROR_PUT_PHOTO. What am I doing wrong? How to catch error?
The problem is you are trying to catch the same error twice. Just remove the catch form the putPhoto function and it should work.
export function putPhoto(urlParams, body) {
return axios.put('/abc/' + urlParams.key + '/def/' + urlParams.id + '/edit', body)
.then(res => {
return res.data;
});
}
Both then and catch promises in putPhoto func were resulting in try statement. Because of that, I give up on try...catch, instead I'm using if...else statement now. Like this:
function* putPhotoWorker(action) {
const payload = yield call(putPhoto, action.urlParams, action.body);
if (isResponseTypeWhatIExpected) {
yield put({ type: PHOTO_UPDATED, payload });
} else {
yield put({ type: API_ERROR_PUT_PHOTO, payload });
}
}