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),
]);
}
Related
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)
I have a react app where I'm creating an unit and it requires authorization.
function* createUnitWorker(action) {
const { payload: {unitDetails, history} } = action;
try {
const unit = yield axios({
method: 'post',
url: `https://myBackend/units/`,
headers: {'Authorization': 'Bearer'+token},
data: {
...unitDetails
}
});
yield put(call(createUnitSuccess, unit));
yield history.push(`/unit/${unit.code}`)
} catch (error) {
yield put(createUnitFailure(error));
yield put(history.push('/error'))
}
}
export function* createUnitWatcher() {
yield takeLatest(unitActionTypes.CREATE_UNIT_START, createUnitWorker);
}
Should I send the token from the component as part of the payload or should I select the token from the user state I have stored in the saga?. Because it seems to me that it is complicated to select the token mapStateToProps and then send it with the action when I could just select the token from within the saga
I would recommend to use axios interceptors so you don't have to manually add the token to each request you send.
const axiosInstance = axios.create({
baseURL: 'https://baseUrl.com',
headers: { "Content-Type": "application/json" }
});
axiosInstance.interceptors.request.use(function (config) {
config.headers.Authorization = 'Bearer'+token;
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
This means you can just use the axiosInstance in each saga, without worrying about the token. Something like this:
axiosInstance.post('/units', payload);
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
}
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 }));
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
}
}