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);
Related
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 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'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.
Heyy so I want to get a json from a request with react-saga! I was wondering how I would get the data that the my saga yields, I have an idea to call a generator function in the componentWillMount that watches for the 'REQUEST_DONE' action with takeLatest and then rerenders.
But I think it would a bad idea to use react-saga in one of my components. Guidance please
My saga file:
export function* Saga() {
yield fetch(url, {
method: 'GET',
headers: {
'Accept': '...',
'Content-Type': 'application/json'
}
})
.then(response => {
return response.json();
})
.then(json => {
return json;
})
.catch(ex => {
console.log('parsing failed', ex)
})
}
export default function* watchAsync() {
console.log(yield Saga().next().value); // gets the value correctly
yield* takeLatest('BLAH', Saga);
}
My component
...
componentWillMount() {
const { store } = this.context;
store.dispatch({type: 'BLAH'});
// I want the request data
}
render() { ... }
EDIT webpackbin DEMO
call fetch and yield result
import { take, put, call } from 'redux-saga/effects';
function fetchData() {
return fetch(url)
.then(res => res.json() )
.then(data => ({ data }) )
.catch(ex => {
console.log('parsing failed', ex);
return ({ ex });
});
}
function* yourSaga(action) {
const { data, ex } = yield call(fetchData);
if (data)
yield put({ type: 'REQUEST_DONE', data });
else
yield put({ type: 'REQUEST_FAILED', ex });
}
export default function* watchAsync() {
yield* takeLatest('BLAH', yourSaga);
}
then connect component and slice needed data
class App extends Component {
...
componentWillMount() {
this.props.dispatch({type: 'BLAH'});
}
render(){
return (<div>Data: {this.props.data}</div>);
}
}
export default connect( state =>({
data:state.data
}))(App);