This code is inside a redux saga file.
I'm trying to send the response back to the saga function but I can't.
new GraphRequestManager().addRequest(infoRequest).start();
infoRequest = new GraphRequest(
'/me', {
httpMethod: 'GET',
version: 'v2.5',
parameters: {
'fields': {
'string': 'email,first_name,last_name,id'
}
}
}, startFacebookRegister);
the facebookRegister function
function * startFacebookRegister(err, res) {
const { id, email, first_name, last_name } = res;
yield put(initFacebookLogin.success(res));
}
If we look at GraphRequestManager.start.
const that = this;
const callback = (error, result, response) => {
if (response) {
that.requestCallbacks.forEach((innerCallback, index, array) => {
if (innerCallback) {
// here it is called as a normal callback, not a generator
innerCallback(response[index][0], response[index][1]);
}
});
}
if (that.batchCallback) {
that.batchCallback(error, result);
}
};
NativeGraphRequestManager.start(this.requestBatch, timeout || 0, callback); } ```
The callback you pass is invoked as a normal Function, as opposed to a GeneratorFunction.
And since they differ in how they are executed, that's why nothing happens, since your GeneratorFunction, when invoked, is just instantiated and sits there until getting garbage collected.
So the preferred option to solve this, which is aligned with the redux-saga flow is using eventChannel, which keeps the flow inside redux-saga and is actually advised for similar cases.
The other one, not so preferable, would be to just call raw store.dispatch, it obviously breaks through the abstraction levels and flow of redux-saga, but still gets the job done, by invoking some other saga action which can handle calling your startFacebookRegister with the response.
EventChannel
We need to wrap the whole GraphRequestManager... block into an eventChannel, so we can redirect the response or error back into saga flow from the vendor control.
Can be something like this:
import {
cancelled,
call,
take,
put
} from "redux-saga/effects";
import {
eventChannel,
END
} from 'redux-saga';
function* startFacebookRegister(err, res) {
const {
id,
email,
first_name,
last_name
} = res;
yield put(initFacebookLogin.success(res));
}
function* graphRequestWrapper() {
return eventChannel(emit => {
const infoRequest = new GraphRequest(
'/me', {
httpMethod: 'GET',
version: 'v2.5',
parameters: {
'fields': {
'string': 'email,first_name,last_name,id'
}
}
}, (err, res) => {
if (err) {
emit(new Error(err));
} else {
emit(res);
}
emit(END);
});
// BTW infoRequest variable should be instantiated before we
// can add it with addRequest, just a side note
new GraphRequestManager().addRequest(infoRequest).start();
return () => {
// clean up
};
})
}
export function* mainSaga() {
const chan = yield call(graphRequestWrapper);
while (true) {
try {
const res = yield take(chan);
yield call(startFacebookRegister, null, res);
} catch (err) {
yield call(startFacebookRegister, err);
} finally() {
if (yield cancelled()) chan.close()
}
}
}
Store Dispatch
And with store, you can export your store from your main.js or index.js where you createStore it with all the reducers and middlewares, and use it here directly.
import store "../exported/from/somewhere";
function* startFacebookRegister({payload}) {
const {
id,
email,
first_name,
last_name
} = payload;
yield put(initFacebookLogin.success(res));
}
function graphRequestWrapper() {
const infoRequest = new GraphRequest(
'/me', {
httpMethod: 'GET',
version: 'v2.5',
parameters: {
'fields': {
'string': 'email,first_name,last_name,id'
}
}
}, (err, response) => {
store.dispatch({type: "INIT_FACEBOOK_LOGIN_REQUEST_SUCCESS_SAGA", payload: {...response}})
});
new GraphRequestManager().addRequest(infoRequest).start();
}
function* mainSaga() {
yield takeLatest("INIT_FACEBOOK_LOGIN_REQUEST_SUCCESS_SAGA", startFacebookRegister);
yield call(graphRequestWrapper);
}
Related
i have a configuration with axios i am testing a feature to receive a list of students from an api, the problem is that it sends me an error:
TypeError:
constants_api_constants__WEBPACK_IMPORTED_MODULE_1_.default.interceptors.request
is not a function
For my axios configuration I use:
const options.GET_ALL_STUDENTS = {
method: "GET",
url: "/Student",
}
const BASE_API_URL = "https://localhost:7072/api";
const api = axios.create({
baseURL: `${BASE_API_URL}`,
});
const getStudents = () => {
return api.interceptors.request(options.GET_ALL_STUDENTS).use(
function request(success) {
return success;
},
function error(err) {
return err;
},
);
};
How I resolve my promise, (without interceptor this work fine):
function* fetchStudents() {
try {
const result1 = yield call(getStudents);
const studentList = createStudentListAdapter(result1.data);
yield put(fetchStudentsSuccess(studentList));
} catch (error) {
yield put(fetchStudentsFailure());
}
}
Interceptors are used to intercept any request/response before it goes to try/catch.
const getStudents = async () => {
try {
const res = await api(options.GET_ALL_STUDENTS);
// logic
} catch (e) {
// handle error
}
};
Interceptor
api.interceptors.request.use(
(request) => {
console.debug("Request", request.url);
return request;
},
(error) => {
console.debug("Request Failed", error.request.data.message);
return Promise.reject(error);
},
);
I am working on a project which began last year, and the developers are not with me. They wrote this code :
import { put, takeLatest, all, call } from 'redux-saga/effects';
import { getUserByUsernameService } from '../../services/userServices';
import 'regenerator-runtime/runtime';
function* fetchUser() {
const response = yield call(getUserByUsernameService);
yield put({ type: 'FETCHED_USER', payload: response.data.user });
}
function* actionWatcher() {
yield takeLatest('FETCHING_USER', fetchUser);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
Code of getUserByUsernameService :
import {
makeGetRequest,
makePostRequest,
makePutRequest,
makeDeleteRequest,
} from '../utils/reqUtils';
export const getUserByUsernameService = (params) => {
let headers = {
"Access-Control-Allow-Origin": "*"
};
makeGetRequest('/user', params, headers);
}
Code of makeGetRequest :
import axios from 'axios';
export const makeGetRequest = (endpoint, params = {}, headers) => {
const options = {
method: 'GET',
headers: { ...headers },
params: params,
url: endpoint,
};
return axiosInstance(options)
.then((resp) => resp.data)
.catch((e) => {
console.log(e);
throw e;
});
};
At runtime I get Cannot read property 'data' of undefined corresponding to the code
yield put({ type: 'FETCHED_USER', payload: response.data.user });
So what is wrong ?
The generator yield returns an object that you can iterate using next method.
I think you should use response.next().valute.data.user.
I think also as you consume the generator in fetchUser you should not yield the result of the call API method.
function* fetchUser() {
const response = call(getUserByUsernameService);
yield put({ type: 'FETCHED_USER', payload: response.next().value.data.user });
}
This is a simple typo — you forgot to return something from your getUserByUsernameService function! You are calling makeGetRequest but not returning the response. You want
return makeGetRequest('/user', params, headers);
I'm implementing get all image by type with redux-saga. I have 2 types, let's say, type kristik and type motif.
When I'm implementing type kristik, it got successful response, but when it comes to type motif, the response is error.
here my code that has the error in console
componentWillMount() => {
const { dispatch } = this.props;
dispatch(getAllMotif());
}
I got error in dispatch(getAllMotif()); in commponentWillMount()
Here my getAllMotif() code
getAllMotif(token) {
const path = `motif`;
const method = 'get';
return request.process(method, path, null, token);
},
Here my sagas getAllMotif code
export function* getAllMotif() {
try {
let { detail } = yield select(state => state.user);
const result = yield call(API.getAllMotif, detail.api_token);
yield put({
type: types.GET_ALL_MOTIF_SUCCESS,
payload: result,
});
} catch (err) {
yield put(handleError(err));
yield put({
type: types.GET_ALL_MOTIF_FAILURE,
payload: err,
});
}
}
here my reducer
case types.GET_ALL_MOTIF_SUCCESS:
return {
...state,
motif: [
...action.payload.data.data
]
};
here my request code
internals.process = (method, path, payload, token, contentType=internals.contentType) => {
const request = {
url: `${API_URL}/${path}`,
method: method,
headers: {
'Content-Type': contentType,
'Accept': 'application/json',
},
};
if (token) {
request.params = {
token: token,
};
}
if (payload) {
request.data = payload;
}
return axios.request(request)
.then(res => {
if (![200, 201].includes(res.status)) {
throw new Error(res.status);
}
return res.data;
})
.catch((error) => {
console.error(method, path, error);
return Promise.reject({
message: error.response.data.error,
code: error.response.status
});
});
};
I don't know why in this type get error, because in type kristik also have very similar code.
You didn't dispatch an action that wasn't a plain object, your function getAllMotif not return a plain object. That lead to the error here.
You should dispatch an normal action
getAllMotifAction(token) {
const path = `motif`;
const method = 'get';
return { type: 'GET_ALL_MOTIF', data: { path, method } };
},
Then in in saga, you catch this action and handle it with your saga function
takeLatest('GET_ALL_MOTIF', getAllMotif);
I'm trying to make two request, one to save an image an other to save a product with the url from obtained from the first requests
This is what I want to do
first: save product image (I'm using axios for requests)
second: get the url from 'productImage' and then include it in the params to save
this is my code
function* createProduct(action) {
const { endpoint, params } = action.payload;
try {
const productImage = yield call(new api().createImage, { endpoint, params });
// I need to wait the url of the image and include it on the params for the second request before is executed
// E.g. params.image = productImage.url
const product = yield call(new api().createProduct, { endpoint, params });
yield put({
type: CREATE_PRODUCT_SUCCEED,
payload: {
product
}
});
} catch (e) {
yield put({
type: CREATE_PRODUCT_FAILED,
payload: {
...e
}
});
}
}
export default function* createProductWatcher() {
yield takeEvery(CREATE_PRODUCT_EXECUTION, createProduct);
}
The best pattern here is to split your saga (createProduct) into two separate sagas:
createImage - will handle the image creation for the product
createProduct - will handle the product creation with the given image
// Creates the product image
function* createImage(action) {
const { endpoint, params } = action.payload;
try {
const image = yield call(new api().createImage, { endpoint, params });
yield put({
type: CREATE_IMAGE_SUCCEED,
// Here you pass the modified payload to createProduct saga
payload: {
endpoint,
params: { image: image.url }
}
});
} catch(e) {
yield put({
type: CREATE_IMAGE_FAILED,
payload: {
...e
}
});
}
}
//Creates the product with the image
function* createProduct(action) {
const { endpoint, params } = action.payload;
try {
const product = yield call(new api().createImage, { endpoint, params });
yield put({
type: CREATE_PRODUCT_SUCCEED,
payload: {
product
}
});
} catch(e) {
yield put({
type: CREATE_PRODUCT_FAILED,
payload: {
...e
}
});
}
}
Then use the builtin yield* operator to compose multiple Sagas in a sequential way.
// Another saga for chaining the results
function* invokeSagasInOrder(sagas) {
try {
const image = yield* createImage();
yield* createProduct(image);
} catch(e) {
console.log(e);
}
}
Welcome to stackoverflow!
I'm trying to access a function that I'm later going to use for changing some database values. However whenever I try to build the project I get:
Error: Route.post() requires callback functions but got a [object Promise]
My initial function is:
import fetch from '../../../../core/fetch';
import history from '../../../../core/history';
export const BOXOFFICE_CHECKING_IN = 'BOXOFFICE_CHECKING_IN';
export const BOXOFFICE_CHECKED_IN = 'BOXOFFICE_CHECKED_IN';
export const BOXOFFICE_CHECKED_IN_ERROR = 'BOXOFFICE_CHECKED_IN_ERROR';
export default function checkIn() {
return async (dispatch, getState) => {
try {
dispatch({ type: BOXOFFICE_CHECKING_IN });
const state = getState();
const {
order: {
id: orderId,
},
} = state;
const response = await fetch(
`/api/event/orders/${orderId}/checkIn`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
order: orderId,
checkedIn: true,
}),
}
);
if (response.status === 200) {
dispatch({ type: BOOKING_CHECKED_IN });
} else {
const errorResponse = await response.json();
if (errorResponse.code === 'card_error') {
dispatch({ type: BOXOFFICE_CHECKED_IN_ERROR });
}
}
} catch (err) {
throw err;
}
};
}
Which then feeds to the api:
import checkIn from '../handlers/api/orders/checkInCustomer';
...
export default (resources) => {
const router = new Router();
...
router.post('/orders/:orderId/checkIn', checkIn(resources));
Which then reaches the final function I wish to use:
export default async function checkIn(req, res) {
console.log('this is working fully');
return true;
}
Any help is appreciated.
The problem is, you want to be passing the function checkIn, but when you call it using checkIn(resources) you're actually passing the return value (A promise that resolves to true).
You should be using:
router.post('/orders/:orderId/checkIn', checkIn);
Now, I'm assuming you want to do this because you want to pass resources into the router.post function, correct? What happens to the request and response objects?
Where does resources go?
v
export default async function checkIn(req, res) {
console.log('this is working fully');
return true;
}
You have a few ways of accomplishing what you're looking for.
Create a resources file, and import it. This is the ideal solution:
const db = mysql.connect(...);
const lang = lang.init();
console.log('This file is only called once!');
export default {
db,
lang,
};
And then in your code (/routes/checkIn.js):
import { db } from '../resources';
export default async function checkIn(req, res) {
//Access db here
//db.query...
}
Wrap your code in an intermediate function:
router.post('/orders/:orderId/checkIn', (req, res) => checkIn(req, res, resources));
Bind() resources to your checkIn function:
const db = mysql.connect(...);
const lang = lang.init();
const resources = {db, lang};
router.post('/orders/:orderId/checkIn', checkIn.bind(resources));
And then in your code (/routes/checkIn.js):
export default async function checkIn(req, res) {
//Access this.db here
//this.db.query...
}