React native saga yield call is not working - reactjs

I am trying to write an api by using redux-saga. I have my servicesSaga.js like this
import { FETCH_USER } from '../actions/actionTypes'
import { delay } from 'redux-saga'
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import { _getUserInformation } from './api'
const getUserInformation = function*(action) {
console.log("FONKSİYONA geldi")
console.log(action)
try {
console.log("try catche geldi")
const result = yield call(_getUserInformation, action)
console.log("result döndü")
if (result === true) {
yield put({ type: FETCH_USER })
}
} catch (error) {
}
}
export function* watchGetUserInformation() {
yield takeLatest(FETCH_USER, getUserInformation)
console.log("WatchUsere geldi")
}
I am trying to yeild call my _getUserInformation method from ./api but yield call method is not working.This is my api.js.
const url = 'http://myreduxproject.herokuapp.com/kayitGetir'
function* _getUserInformation(user) {
console.log("Apiye geldi" + user)
const response = yield fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: user.email,
})
})
console.log(response.data[0])
return yield (response.status === 201)
}
export const api ={
_getUserInformation
}
Thank you for your helps from now.

generator function must be define as function* yourFunction() {} try this changes.
servicesSaga.js
function* getUserInformation(action) {
try {
const result = yield _getUserInformation(action) //pass user here
if (result) {
yield put({ type: FETCH_USER })
}
} catch (error) {
}
}
export function* watchGetUserInformation() {
yield takeLatest(FETCH_USER, getUserInformation)
}
api.js
const url = 'http://myreduxproject.herokuapp.com/kayitGetir'
function* _getUserInformation(user) {
const response = yield fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: user.email,
})
})
console.log('response',response);
return response;
}
export {
_getUserInformation
}

Related

The saga yield is not working on first action

I have a simple login system, and when i press submit it does not work the first time, the session is not created and the data are not returned. I believe there is something wrong in my saga file.
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
import axios from "axios";
function run(data){
var actionUrl = '/pages/login';
return axios ({
method: 'POST',
url: process.env.REACT_APP_API_URL + actionUrl,
data: {
data
},
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }
});
}
function* postToApi(payload) {
try {
const resp = yield call(run, payload.payload.data);
if(resp.data.json.error == false) {
if(resp.data.json.user) {
// Set the session
localStorage.setItem("user", JSON.stringify(resp.data.json.user));
}
}
yield put(actions.postToApiSuccess(resp.data.json));
} catch (error) {
yield put(actions.postToApiError(error));
}
}
export default function* rootSaga() {
yield all([
takeEvery(actions.POST_TO_API, postToApi),
]);
}

argument of type {context, fn} has undefined or null fn with sagas

I use redux and sagas, when i do my API call, the api response is 200 but i'm getting in the log "argument of type {context, fn} has undefined or null fn".
Why? Basically my yield put(actions.postToApiSuccess(resp.data.json)); is not called
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
import omit from 'lodash/omit';
import axios from "axios";
function run(data){
return axios ({
method: 'POST',
url: 'http://creaz:81/agents/api/pages/subscribe',
data: {
data
},
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }
});
}
function* postToApi(payload) {
try {
const resp = yield call(run(payload.payload.data));
yield put(actions.postToApiSuccess(resp.data.json));
} catch (error) {
console.log('error');
console.log(error);
yield put(actions.postToApiError(error));
}
}
export default function* rootSaga() {
yield all([
takeEvery(actions.POST_TO_API, postToApi),
]);
}
//change this this line of code, should work fine
From >>>
const resp = yield call(run(payload.payload.data))
To >>>
const resp = yield call(run,payload.payload.data)

How do I create a Generic postToAPI(route, package2send) function in React?

In our project we are using the MERN stack
I want to create a generic function whose input is the path to any api endpoint in our server and the JSON package to POST to the server. I want it to return the JSON sent back from the server.
That way when we are developing our mobile app and web app, we can simply use this function for all of our api endpoint POSTs.
I'm very new to using React/React-Native so I'm sure that I'm not understanding some sort of key concept.
Here is what I have so far:
import React from 'react';
// returns whatever the respective apiEndpoint is suppose to return
function postToAPI(route, package2send)
{
async() =>
{
try
{
const payload = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: package2send
}
const res = await fetch(route, payload);
console.log(res);
const data = await response.json();
console.log(data);
return data;
}
catch(error)
{
console.error(error);
}
}
}
export default postToAPI;
Whenever I call this function from my Login.js after I
import { postToAPI } from './postToAPI'
I get this error: 'TypeError: Object(...) is not a function'
I'm sure there are multiple things wrong with this code, so if someone could steer me in the right direction, it would be greatly appreciated.
If you export the function as default, you must import without bracket like that.
import postToAPI from './postToAPI';
If you would like to write a generic API call class, I advise you this class which I wrote before.
import { BASE_URL } from "../config";
import { Actions } from "react-native-router-flux";
import { deleteUserInfo } from "./SessionHelper";
const API_URL = BASE_URL;
class ApiHelper {
private accessToken?: string;
constructor() {
this.accessToken = undefined;
}
setAccessToken = (accessToken: string) => {
this.accessToken = accessToken;
};
getAccessToken = () => {
return this.accessToken;
};
getRequest = async (endpoint: string) => {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
method: "GET",
headers: {
"x-access-token": `${this.accessToken}`
}
});
const responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
};
postRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
return error;
}
};
patchRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}/${endpoint}`, {
method: "PATCH",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
}
};
deleteRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}/${endpoint}`, {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
}
};
}
export const APIHelper = new ApiHelper();

how to pass returned fetch data to a reducer using redux saga

I'm doing a fetch request that makes a new user in my database. All of it works and a new user is made/api-key returned.
The problem is that i am unable to pass the received response of my fetch request to my reduces.
I'm wondering if I should call another action as a response to my successful fetch request that triggers a reducer and takes the response of the request as payload.
Or if I am able to pass the response of the fetch request to the reducer instantly.
Here is my SAGA:
import { call, put, takeEvery, takeLatest, delay } from 'redux-saga/effects';
import {REGISTER} from '../redux/actions/loginAPIcall'
function* callAPIregister(){
const json = yield fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}),
})
.then((response) => response.json())
.then(data => {
console.log(data)
})
yield put({type: 'REGISTER_SAGA', payload: json})
}
export function* watchAPIcall(){
yield takeEvery(REGISTER, callAPIregister)
}
and below is my reducer:
import {REGISTER, LOGIN} from '../actions/loginAPIcall'
const initialState = {
apiCalling: false,
occupation: null
}
function addAPIcall(state = initialState, action, payload){
console.log('inside the api reducer')
switch(action.type){
case "REGISTER_SAGA":
console.log('inside register_saga reducer', payload)
return {
apiCalling: true,
occupation: 'REGISTER'
}
case LOGIN:
return {
apiCalling: true,
occupation: 'LOGIN'
}
default:
return state;
}
}
export default addAPIcall
when loggin the reducer payload now it says undefined.
yield by itself will wait until Promise is resolved if Promise will be returned from the yielded statement. So correct callAPIregister will be
function* callAPIregister(){
// yield will wait for Promise to resolve
const response = yield fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}),
})
// Again yield will wait for Promise to resolve
const data = yield response.json()
console.log(data)
yield put({type: 'REGISTER_SAGA', payload: data})
}
And also I recommend to consider using call in yield statements. It is for easier unit testing
In my opinion, this thing will work for you. Made 'FETCH_FAILED' type well if there's any error in fetching then you can catch that error. So, make one more variable in your reducers initial_state object.
sagas.js
import { call, put, takeLatest, takeEvery } from 'redux-saga/effects';
import {REGISTER} from '../redux/actions/loginAPIcall';
function getData(payload){
return fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(response => response.json())
.then(json => json)
.catch(error => {
throw error;
});
}
function* callAPIregister(){
try{
const payload = {
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}
const response = yield call(getData, payload);
//In this please check what is the name of your data variable
//Eg if its message then you can
console.log(response);
//use response: response.message
yield put({type: 'REGISTER_SAGA', response: response})
} catch (error){
yield put({ type: 'FETCH_FAILED', error });
}
}
export function* watchAPIcall(){
yield takeEvery(REGISTER, callAPIregister)
}
In your reducer you can create a variable in initial state object and then in your 'REGISTER_SAGA' capture the data that we got from our saga
reducer.js
const initialState = {
apiCalling: false,
occupation: null,
data: []
}
case "REGISTER_SAGA":
console.log('inside register_saga reducer', payload)
return {
apiCalling: true,
occupation: 'REGISTER',
data: action.response
}
import { takeEvery, put, call } from "redux-saga/effects";
import { AnyAction } from "redux";
const users = [
{
id: 1,
name: "Keshav Gera",
email: "Keshav.Gera#gmail.com"
},
{
id: 2,
name: "Happy Gera",
email: "Happy.Gera#gmail.com"
}
];
yield put(getUsersSuccess({ users }));

How to handle common fetch actions inside saga

I'm developping an API consuming web front site.
The problem
All my API saga were like this :
export function* login(action) {
const requestURL = "./api/auth/login"; // Endpoint URL
// Select the token if needed : const token = yield select(makeSelectToken());
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + btoa(JSON.stringify({ login: action.email, password: action.password })),
}
};
try {
// The request helper from react-boilerplate
const user = yield call(request, requestURL, options);
yield put(loginActions.loginSuccess(user.token);
yield put(push('/'));
} catch (err) {
yield put(loginActions.loginFailure(err.detailedMessage));
yield put(executeErrorHandler(err.code, err.detailedMessage, err.key)); // Error handling
}
}
And I had the same pattern with all my sagas :
Select the token if I need to call a private function in the start of the saga
const token = yield select(makeSelectToken());
Handle errors on the catch part
export const executeErrorHandler = (code, detailedMessage, key) => ({
type: HTTP_ERROR_HANDLER, status: code, detailedMessage, key
});
export function* errorHandler(action) {
switch (action.status) {
case 400:
yield put(addError(action.key, action.detailedMessage));
break;
case 401:
put(push('/login'));
break;
//other errors...
}
}
export default function* httpError() {
yield takeLatest(HTTP_ERROR_HANDLER, errorHandler);
}
The solution I came up with
Remove the token parts and error handling part and puth them inside the call helper :
export function* login(action) {
const url = `${apiUrl.public}/signin`;
const body = JSON.stringify({
email: action.email,
password: action.password,
});
try {
const user = yield call(postRequest, { url, body });
yield put(loginSuccess(user.token, action.email));
yield put(push('/'));
} catch (err) {
yield put(loginFailure());
}
}
// post request just call the default request with a "post" method
export function postRequest({ url, headers, body, auth = null }) {
return request(url, 'post', headers, body, auth);
}
export default function request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
return fetch(url, addHeader(options, auth)) // add header will add the token if auth == true
.then(checkStatus)
.then(parseJSON)
.catch(handleError); // the error handler
}
function handleError(error) {
if (error.code === 401) {
put(push('/login')); // <-- Here this doesn't work
}
if (error.code == 400) {
displayToast(error);
}
}
function addHeader(options = {}, auth) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (auth) {
const token = yield select(makeSelectToken()); // <-- here it doesn't work
newOptions.headers.Authorization = `Bearer ${auth}`;
}
return newOptions;
}
I know the solution is between generator functions, side effects, yield call / select but I tried so many things it didn't work. For example, if I wrap everything inside generator functions, the token load is executed after the code continues and call the API.
Your help would be appreciated.
You need to run any and all effects (e.g. yield select) from a generator function, so you'll need generators all the way down to the point in your call stack where you yield an effect. Given that I would try to push those calls as high as possible. I assume you may have getRequest, putRequest etc. in addition to postRequest so if you want to avoid duplicating the yield select you'll want to do it in request. I can't fully test your snippet but I believe this should work:
export function* postRequest({ url, headers, body, auth = null }) {
return yield call(request, url, 'post', headers, body, auth); // could yield directly but using `call` makes testing eaiser
}
export default function* request(url, method, headers, body, auth = null) {
const options = { method, headers, body };
const token = auth ? yield select(makeSelectToken()) : null;
try {
const response = yield call(fetch, url, addHeader(options, token));
const checkedResponse = checkStatus(response);
return parseJSON(checkedResponse);
} catch (e) {
const errorEffect = getErrorEffect(e); // replaces handleError
if (errorEffect) {
yield errorEffect;
}
}
}
function addHeader(options = {}, token) {
const newOptions = { ...options };
if (!options.headers) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
}
if (token) {
newOptions.headers.Authorization = `Bearer ${token}`;
}
return newOptions;
}
function getErrorEffect(error) {
if (error.code === 401) {
return put(push('/login')); // returns the effect for the `request` generator to yeild
}
if (error.code == 400) {
return displayToast(error); // assuming `displayToast` is an effect that can be yielded directly
}
}

Resources