I'm a newbie to redux-saga. Currently I'm working on a simple login example.
Take a look at these function:
function sendLoginRequest(data) {
const headers = { 'Accept': 'application/json' };
const url = LOGIN_URL;
const serialize = new FormData(data.event.target);
const loginData = {
username: serialize.get('email'),
password: serialize.get('password'),
client_secret: APP_SECRET_KEY,
client_id: APP_SECRET_ID,
grant_type: PASSWORD_GRANT_TYPE,
scope: '*',
}
return axios({
method: 'POST',
url: url,
data: loginData,
headers: headers,
});
}
export function* loginRequest(data) {
yield takeLatest(LOGIN_REQUEST, data => {
const response = sendLoginRequest(data);
console.log(response);
response
.then(function* (data) {
console.log(data);
yield put(LOGIN_SUCCESS, data.data);
})
.catch(function* (err) {
console.log(err);
yield put(LOGIN_FAILURE, err.response);
});
});
}
It's work perfect if I run middleware like this:
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(loginRequest);
But then I add a new rootSaga:
export default function* rootSaga() {
yield all([
fork(loginRequest),
fork(loginSuccess),
fork(loginFailure)
]);
}
And I run the rootSaga instead of loginRequest saga:
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);
And now these new code doesn't work at all.
when I try to console.log(response); in loginRequest generator function, it's shown that the promise has been resolved. And it doesn't run the then-catch.
Can anyone help me clear this out?
Thank you~
Please try this:
export function* loginRequest(data) {
yield takeLatest(LOGIN_REQUEST, data => {
const response = yield call(sendLoginRequest, data);
if (response.status >= 200 && response.status < 300) {
yield put(LOGIN_SUCCESS, data.data);
return;
}
yield put(LOGIN_FAILURE, err.response);
});
}
The thing to note is sendLoginRequest returns a promise. redux-saga is designed to work with promises without using .then(). You can yield call() any function that returns a promise and redux-saga will wait for the promise to resolve before executing the next line of code.
Related
So, I am learning NodeJs by creating this backend that fetches some data from a third-party API, the API requires auth. I couldn't figure out how to avoid sending an auth request to the third-party API whenever I wanted to fetch data from it. is there any way I could store the auth state in the app?
const axios = require("axios");
const AUTH_URL = process.env.AUTH_URL;
const REPORT_BASE_URL = process.env.REPORT_BASE_URL;
const X_API_KEY = process.env.X_API_KEY;
const getCompanies = async (req, res) => {
let idToken;
// auth
const authPayload = JSON.stringify({
// ...
});
const config = {
method: "post",
// ...
};
try {
const { data } = await axios(config);
idToken = data.idToken; // set idToken necessary for fetching companies
} catch (error) {
console.log(error);
}
// get company by full text query
const { full_text_query } = req.query;
if (!full_text_query)
return res.send("No full_text_query parameter provided");
try {
const { data } = await axios.get(
`${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`,
{
headers: {
"x-api-key": X_API_KEY,
Accept: "application/json",
authorization: idToken,
},
}
);
res.status(200).json(data);
} catch (error) {
console.log(error);
}
};
module.exports = {
getCompanies,
};
You can break out a function like fetchIdToken and store a Promise that resolves with the idToken in memory.
let idTokenPromise;
async function fetchIdToken () {
if (idTokenPromise) return idTokenPromise;
return idTokenPromise = new Promise(async (resolve) => {
...
resolve(data.idToken);
})
}
You can then use await fetchIdToken() at the start of getCompanies.
You can also just store the idToken in memory. This is slightly simpler, but does mean that you can have a race-condition when multiple getCompanies requests happen at the same time:
let idToken;
async function fetchIdToken () {
if (idToken) return idToken;
...
idToken = data.idToken;
return idToken;
}
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 make a post request in my application, using redux saga middleware.
Now, the code related to my post request looks like this:
function* postNewMessage(newMessage) {
console.log(newMessage)
const {var1, var2} = newMessage;
try {
const data = yield call(() => {
return fetch(myapi, {
method: 'POST',
headers: {
Accept: `application/json`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
first: var1,
second: var2
}),
})
});
console.log(data);
} catch (error) {
console.log(error);
}
}
function* addNewMessageSaga(action) {
console.log(action)
try {
yield postNewMessage(action.newMessage)
}catch (e) {
console.log(e)
}
}
function* watchNewMessage() {
takeEvery(POST_MESSAGE, addNewMessageSaga)
}
//bellow is my action which i call in my component
export const postMessage = (newMessage) => {
console.log(newMessage)
return {
type: POST_MESSAGE,
newMessage
};
};
This is all my code related with post request. Now i call the action inside component like:
postMessageOnApi({
var1: selector.data,
var2:selector.data1[0]
})};
When i click on the button to post my data, i get the values only inside postMessageOnApi, there console.log() shows my values, but in function * postNewMessage.. i don't get data.
Who knows where i made a mistake and how to solve the issue?
It looks like you forgot to use yield before takeEvery
function* watchNewMessage() {
yield takeEvery(POST_MESSAGE, addNewMessageSaga)
}
export function* onFetchTree() {
yield takeLatest('FETCH_TREE', function* () {
try {
const response = yield call(fetch, '/myApi/user', {
method: 'GET',
headers: {
accept: 'application/json'
}
})
const responseBody = response.json();
yield put({ type: 'SET_TREE', payload: responseBody });
} catch (e) {
// yield put(fetchFailed(e));
return;
}
});
}
Learning to work with sagas, stuck on getting the actual data into my redux store. The above code which sends responseBody to the payload gives me a Promise object (because .json() returns that) which is great, except that I can't access the resolved Promise. I ended up on What does [[PromiseValue]] mean in javascript console and how to do I get it but this doesn't seem to work for me. I've tried adding .then() in a few ways, no luck. It seems to prevent the generator function from running at all.
If I just use response I get a Response object, which doesn't have the payload. What am I missing here? How do I get the right payload?
You need to wait for the server to send back the response.
export async function* onFetchTree() {
yield takeLatest('FETCH_TREE', function* () {
try {
const response = yield call(fetch, '/myApi/user', {
method: 'GET',
headers: {
accept: 'application/json'
}
})
const responseBody = await response.json()
yield put({ type: 'SET_TREE', payload: responseBody )}
};
} catch (e) {
// yield put(fetchFailed(e));
return;
}
});
}
I followed a pattern I found on this page that ended up working for me. I don't fully understand why the fetchTree helper is needed, but it doesn't work without it.
https://www.sigient.com/blog/managing-side-effects-with-redux-saga-a-primer-1
function fetchJson(url) {
return fetch(url, {
method: 'GET',
headers: {
accept: 'application/json'
}
})
.then(response => {
if (!response.ok) {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
return response.json();
});
}
function fetchTree() {
return fetchJson('/myApi/user');
}
export function* onFetchTree() {
try {
const tree = yield call(fetchTree);
yield put({ type: 'SET_TREE', payload: tree });
} catch (e) {
yield put({
type: 'ERROR',
payload: e,
error: true,
});
}
}
I need to send an axios request passing a token, this token is saved on my AsyncStorage. The problem is, when i make the call looks like its been sended without the AsyncStorage return the token, also the then/catch do not trigger.
Code example:
export const verificarPreco = (produto, estabelecimento) => {
return async dispatch => {
axios({
method: "get",
url: `${API}preco/verificarPreco/?produto=${produto}&estabelecimento=${estabelecimento}`,
headers: {
"x-access-token": await AsyncStorage.getItem("#Offer4All:token")
}
})
.then(response => {
verificarPrecoSucesso(response.data, dispatch);
})
.catch(error => {
verificarPrecoErro(error.response, dispatch);
});
};
};
You could use just async/await instead of handling the promises manually, and putting a try/catch around that.
export const verificarPreco = (produto, estabelecimento) => {
return async dispatch => {
try {
const token = await AsyncStorage.getItem("#Offer4All:token");
const request = await axios({
method: "get",
url: `${API}preco/verificarPreco/?produto=${produto}&estabelecimento=${estabelecimento}`,
headers: {
"x-access-token": token
}
});
const response = await request.json();
verificarPrecoSucesso(response.data, dispatch);
} catch (error) {
verificarPrecoErro(error.response, dispatch);
}
};
};