Redux Saga, why do I receive an error when unit testing a saga from a file containing a fat arrow function? - redux

I have encountered the following scenario when trying to test a saga from redux saga.
Some package information:
I use redux-saga#0.15.6, redux#3.7.2 with node v9.5.0 and npm 5.6.0.
I have the following structure:
sagas
index.js
index.spec.js
Inside index.js, I define my sagas. A basic idea of what I have there is this:
function doSomething() {
/* Some code */
}
function* notExportedSaga() {
yield takeLatest(SOME_ACTION, doSomething)
}
export function* exportedSaga(action) {
yield put({ type: OTHER_ACTION, payload: somePayload })
}
In index.spec.js, I want to test my sagas. I have a import at the top of my file:
import { exportedSaga } from './index'
With the structure and code I described, this works just fine. However, if I change doSomething from being defined like it is to a fat arrow function:
const doSomething = () => {
/* Some code */
}
What will happen is, when running the unit tests, I will get this error:
console.error node_modules/redux-saga/lib/internal/utils.js:240
uncaught at rootSaga
at rootSaga
at rootSaga
ReferenceError: doSomething is not defined
Do you know why this is happening?
I have open a issue , because I don't know if this is a bug or not.

Function declarations like
function doSomething() { }
are being hoisted to the top
Function expressions like
const doSomething = () => { }
are not being hoisted
That's why you're getting doSomething is not defined - both notExportedSaga and exportedSaga functions are being hoisted to the top while the expression function const doSomething = () => { } is not and it is undefined on your generator functions invocations.
Here's a cool article about hoisting if you want to know more :) https://scotch.io/tutorials/understanding-hoisting-in-javascript

Related

Using react-papaparse in the Next.js getStaticProps - XMLHttpRequest is not defined

I am trying to use papaparse inside my Next.js page. I am using react-papaparse package which is a React wrapper for Papaparse. I am trying to parse remote CSV file inside getStaticProps() function but the error I get is XMLHttpRequest is not defined.
I know that getStaticProps() is executed on Node.js but I don't know how to use react-paparse correctly to avoid XMLHttpRequest call. Here is my code:
// index.js Next.js page
import { usePapaParse } from "react-papaparse";
export async function getStaticProps(context) {
const { readRemoteFile } = usePapaParse();
readRemoteFile(googleSheetUrl, {
complete: (results) => {
console.log("PAPAPARSE::complete", results)
},
});
}

Why after yield all([someGenerator]), another function is not called

I try to call another function after yield all([])
When I replace call() effect with fork(), it works. I know that fork() is no-blocking effect. But assuming that all the effects are resolved (using call()), next line should be also called.
After all() effect I try to call another generator with WebSockets. Maybe it is not the best place, but I don't know why it doesn't work anyway.
export function* someGenerator () {
try {
return true
} catch (e) {}
}
export function* watchSomeAction() {
yield takeEvery('someAction', someGenerator )
}
export default function* rootSaga () {
yield all([call(watchSomeAction)])
yield call(anotherGenerator) \\ never called even when all the effects are
resolved
}```

Promises in angular2 with firebase

Can someone explain how to correctly implement promise in Angular2 and Firebase.
I've read some articles such as this https://www.firebase.com/blog/2016-01-21-keeping-our-promises.html
in my app.component.ts file i have this
export class AppComponent{
players: Player[];
constructor(private _playerService: PlayerService){}
getPlayers(){
this._playerService.getPlayers().then(res => this.players = res);
}
ngOnInit(){
this.getPlayers();
}
}
inside the player.service.ts file I have this
getPlayers() {
this.playersRef.once('value', function (snap){
return snap.val();
});
}
I always get TypeError: this._playerService.getPlayers(...) is undefined
I also tried this as the article on top suggests
getPlayers() {
var data;
this.playersRef.once('value').then( function (snap){
data = snap.val();
});
return data;
}
But then i get this: Error: Query.once failed: Was called with 1 argument. Expects at least 2. in [null]
I'm not sure how the article is working at all with .once('value').then()
Problem occurs because you are trying to using .then over a method which isn't using promise. Basically missed to return promise from getPlayers method, you should return promise from there to perform promise chaining using .then method over it.
Also don't use callback to return value from it(because callback are not capable of returning anything from it), use .then function over .once so that you can extend promise chain & will be able to return out data correctly.
Code
getPlayers() {
//returned promise here
return this.playersRef.once('value').then((snap) => {
return snap.val();
});
}

Am I using Redux correctly?

this is just a question,
I'd love to double check if I'm doing things right. I'm coming from ages of different frameworks, and I def want to avoid bad practices in the early stage.
I'm using this boilerplate: https://github.com/erikras/react-redux-universal-hot-example
and I'm writing in ES7.
I created:
- 1 reducer: earlyUser.js
- 1 container: landingPage.js
- 1 component: registrationForm.js
In the landingPage, I'm including the methods from reducer in this way:
import { saveEmail, savePreferences, checkEmailExists } from 'redux/modules/earlyUser';
and I declare some handles
handleSubmitEmail(data) {
this.props.saveEmail(data);
}
handleSubmitPreferences(data) {
//some data manipulation
this.props.savePreferences(data);
}
and in the JSX part I just pass to my component the handlers:
<registrationForm submitEmail= {::this.handleSubmit} etc... >
Now inside the component, I linked the form submission to this handler:
submitEmail() {
if (this.validateEmail(this.state.email)) {
this.props.submitEmailHandler(this.state);
}
}
Now my question is, where should I attach the .then and .catch of the promise returned ?
Ideally I'd like to do inside the component.js something like
this.props.submitEmailHandler(this.state).then( function emailRegisterCallback(){
// move to next step
}).catch( function errorHandler(error){
// there was an error
}
Is that correct?
Also, is there the right syntax to handle promises in ES7 ?
You normally handle the async aspects in your action creators:
See this code from the async redux example:
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit));
return fetch(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)));
};
}
When the promise resolves, you should dispatch another action to update the state with the successful result or the error.

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources