Api calls in redux-saga crashing redux-devtools-extension - react-redux

I'm trying to make some api calls using axios and redux-saga. This is nothing I haven't done before, and redux devtools usually handles this just fine. For some reason in the current application I'm working on, any actions that trigger a saga, which then make an api call, seems to crash my redux-devtools-extension. I know that redux-devtools-extension has always been a bit buggy, but I can't put my finger on why these actions might be crashing it. Here's a typical saga:
function* serverRefresh(): Generator {
try {
yield call(axios.get, "/api/restart"); // <------ crashes devtools extension
} catch (e) {
console.log(e);
}
}
function* watchServerRefresh(): Generator {
yield takeEvery(ActionTypes.RESTART_SERVER, serverRefresh);
}
export function* serverSagas(): Generator {
yield all([fork(watchServerRefresh)]);
}
Note that if I comment out the axios call, the extension works fine, properly registering actions. Other actions not coming from sagas have no problem. Switching from axios to fetch does not help. There's not a lot of logic here that might cause an infinite loop or trigger CPU overload - its a simple api call.
Here's how I set up my store and devtools extension:
function* rootSaga(): Generator {
yield all([fork(serverSagas), fork(campaignSagas)]);
}
const sagaMiddleware = createSagaMiddleware();
const rootReducer = combineReducers({ ...reducers });
export const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
Why might non-saga actions, or saga non-api-call actions, be working fine, but pretty much any api call made with fetch or axios cause the devtools to freeze and crash?

The issue, which I didn't mention in my question, was that the redux store would freeze on any action that ocurred after action X. Action X was an action that stored a reference to a circular object in the store. (Its a leaflet map instance, for those interested.) While that action did not crash the devtools, and I'm even able to examine the leaflet map instance circular object with the devtools just fine, any action that ocurred after that circular object is stored in the store. So this really had nothing to do with sagas or api calls, but rather with keeping circular objects in the store, which we all know is a bad idea. I guess I can move that object over to a react context object instead.

Related

Using `createMockClient` for testing non react code?

I have mixed application that uses Apollo for both React and non-react code.
However, I can’t find documentation or code examples around testing non-react code with the apollo client,not using MockedProvider. I did, however, notice that apollo exports a mock client from the testing directory.
import { createMockClient } from '#apollo/client/testing';
I haven’t found any documentation about this API and am wondering if it’s intended to be used publicly and, if not, what the supported approach is for this.
The reason I need this is simple: When using Next.js’ SSR and/or SSG features data fetching and actual data rendering are split into separate functions.
So the fetching code is not using React, but Node.js to fetch data.
Therefore I use apolloClient.query to fetch the data I need.
When trying to wrap a react component around that fetching code in a test an wrap MockedProvider around that the apolloClient’s query method always returns undefined for mocked queries - so it seems this only works for the useQuery hook?
Do you have any idea how to mock the client in non-react code?
Thank you for your support in advance. If you need any further information from me feel free to ask.
Regards,
Horstcredible
I was in a similar position where I wanted to use a MockedProvider and mock the client class, rather than use useQuery as documented here: https://www.apollographql.com/docs/react/development-testing/testing/
Though it doesn't seem to be documented, createMockClient from '#apollo/client/testing' can be passed the same mocks as MockedProvider to mock without useQuery. These examples assume you have a MockedProvider:
export const mockGetAssetById = async (id: Number): Promise<any> => {
const client = createMockClient(mocks, GetAsset)
const data = await client.query({
query: GetAsset,
variables: id,
})
return data
}
Accomplishes the same as:
const { data } = useQuery(
GetAsset,
{ variables: { id } }
)

Top Level Await for AsyncStorage in React Native

I am trying to implement top level await in my react native & expo environment so I can pull data from AsyncStorage and store it in a variable to use globally in my app.
I'm running the app on an android emulator. All I want to do is get the data and use it in my application, but the the variable always returns an empty object when called outside the async function. It seems to apply a value to the variable before the data from AsyncStorage is done loading.
async function getData(){
try {
const value = await AsyncStorage.getItem('redditInsights')
console.log('----------- getStore insights ---------')
const insights = JSON.parse(value)
console.log(insights) // -------------> returns desired data
return insights
}
catch (error) {
console.log(error)
}
}
const outsights = getData()
console.log("-------------------- outsights---------------------")
console.log(outsights) // ----------------------> returns empty object
I know I can pull the data inside an async function, but I can't use that data anywhere except inside the async function. I can't call a variable declared inside async outside of the function without it trying to apply a value to the variable before the data is even pulled.
Ideally I would simply store the data into a variable to use wherever I want without an async function like so, but this requires top level await support:
const value = await AsyncStorage.getItem('redditInsights')
const insights = JSON.parse(value)
I tried implementing top level await through numerous flags (--harmony-top-level-await, --experimental-repl-await, --experimental-top-level-await) at npm start like so: npm start --flag to no avail. I also upgraded to node v14.15.1 which is suppose to support top level await, but I still get an error.
How do I implement top level await in my react native & expo environment so I can use the data in my AsyncStorage
Or if anyone knows a better way to get my data from AsynStorage, I'm more than willing to try it out!!
The simple answer is this is not possible.
There's a more detailed answer about top level await in react-native here which says that React Native doesn't meet the requirements for top level await (uses node modules not ECMAScript native modules).
But your code sample has a mistake, you could declare a top level variable insights and assign the result of your async data to it. Your mistake is using the return value of getData you would need to declare a top level variable with let and then assign the result to it (so instead of return insights) you would do something like topScopeInsights = insights after a let topScopeInsights.
But, this doesn't make any sense, because you won't know if the data has arrived yet. The app will boot with your variable undefined and then you'll need to check if it has arrived or not. That's what promises are for.
So, back to my original answer, this is not possible. :-(

How do I add axios to react-redux-universal-hot-example starter kit?

So I picked react-redux-universal-hot-example as a starter kit.
I want to make ajax calls to 3rd party API.
(I want to use OpenWeatherMap API)
I want to use axios and react-promise middleware.
The first part installation is easy:
npm install --save redux-promise
npm install --save axios
Then I need to apply redux-promise middleware ... I think it goes into create.js somehow.
So I have several questions (all specific to react-redux-universal-hot-example) :
Is it the right way to do 3rd party calls using axios with redux-promise or the kit already have another way to do ajax requests?
Is create.js the right place to add redux-promise middleware?
Also, is there a concise syntax to apply multiple middlewares at the same time?
I am not 100% sure if you want to use react-promise or redux-promise. I am assuming the redux version. Which yea that create.js file is the right place to add it. You import a middleware from redux-promise and then add it to the lost of middlewares on line 9
import promiseMiddleware from 'redux-promise';
const middleware = [createMiddleware(client), reduxRouterMiddleware, promiseMiddleware];
So to answer your questions:
1. I am not 100% sure if the starter kit has a what to fetch 3rd party data. However using axios with redux-promise is exactly what I do.
2. Yes, see above on how.
3. Yep and that starter kit is showing one way which is creating an array of middlewares and then using the new spread operator it feed them into the applyMiddleware function. Which is a curried function that will then accept the createStore function. This is shown in the create.js file on line 21. Which is what the finalCreateStore will be during production.
4. The way I have been using this is with redux-actions which create Flux Standard Actions So..
// actionCreator
import { createAction } from 'redux-actions';
import { FETCH_DATA } from '../constants; // a file that exports constants
const fetchData = createAction(FETCH_DATA);
// reducer
const reducer = (state, action) => {
switch(action.type) {
case FETCH_DATA:
return {
...state,
fetchedData: action.payload.data
};
default:
return state;
}
}
// then dispatch the promise and not an action
store.dispatch(fetchData(axios.get('some_route')));
Then redux-promise 'intercepts' the promise and will resolve it. Once the promise resolves the action is then re-dispatched with the results in the payload. With axios you will get back an object that had the data you want as a property of that object. Which is shown is in the reducer with action.payload.data
A project I am working on that has this as an example.
Please not the actions have been wrapped with the dispatch call so all I have to do is call the actionCreators directly to get the same result as dispatching. This is explained in redux docs on bindActionCreators.
actionCreator
reducer
Component is where I dispatch the action
Where I hook up the middleware <-- similar to your create.js file
Again by dispatching the promise redux-promise intercepts(my way of understanding) the promise, resolves it and then dispatches the same action again but with the results of the promise on the payload property of the action object.

Meteor 0.5.9: replacement for using Session in a server method?

So, I was attempting to do something like the following:
if(Meteor.isServer){
Meteor.methods({connect_to_api: function(vars){
// get data from remote API
return data;
}});
}
if(Meteor.isClient){
Template.myTpl.content = function(){
Meteor.call('connect_to_api', vars, function(err,data){
Session.set('placeholder', data);
});
return Session.get('placeholder');
};
}
This seemed to be working fine, but, of course, now breaks in 0.5.9 as the Session object has been removed from the server. How in the world do you now create a reactive Template that uses a server-only (stuff we don't want loading on the client) method call and get data back from that Method call. You can't put any Session references in the callback function because it doesn't exist on the server, and I don't know of any other reactive data sources available for this scenario.
I'm pretty new to Meteor, so I'm really trying to pin down best-practices stuff that has the best chance of being future-proof. Apparently the above implementation was not it.
EDIT: To clarify, this is not a problem of when I'm returning from the Template function. This is a problem of Session existing on the server. The above code will generate the following error message on the server:
Exception while invoking method 'connect_to_api' ReferenceError: Session is not defined
at Meteor.methods.connect_to_api (path/to/file.js:#:#)
at _.extend.protocol_handlers.method.exception ... etc etc
Setting the session in the callback seems to work fine, see this project I created on github: https://github.com/jtblin/meteor_session_test. In this example, I return data in a server method, and set it in the session in the callback.
There are 2 issues with your code:
1) Missing closing brace placement in Meteor.methods. The code should be:
Meteor.methods({
connect_to_api: function(vars) {
// get data from remote API
return data;
}
});
2) As explained above, you return the value in the session, before the callback is completed, i.e. before the callback method had the time to set the session variable. I guess this is why you don't see any data in the session variable yet.
I feel like an idiot (not the first time, not the last). Thanks to jtblin for showing me that Session.set does indeed work in the callback, I went back and scoured my Meteor.method function. Turns out there was one spot buried in the code where I was using Session.get which was what was throwing the error. Once I passed that value in from the client rather than trying to get it in the method itself, all was right with the world.
Oh, and you can indeed order things as above without issue.

Prevent re-loading of javascript if functions already exist. Otherwise ensure synchronous loading

Using JQuery.load(), I change the content of my website's mainWindow to allow the user to switch between tabs. For each tab, there is one or multiple scipts that contain functions that are executed once the tab content is loaded.
Obviously when switching to the tab for the first time, the script has to be fetched from the server and interpreted, but this shouldn't happen if the user switches back to the tab later on. So, to put it short:
Load() html
make sure javascript functions exist, otherwise load script and interpret it.
call a a function on the javascript after the DOM is rebuilt.
Step one and two have to be complete before step 3 is performed.
At the moment, I am using nested callbacks to realize this:
function openFirstTab(){
$("#mainWindow").load("firstTab.php", function(){
if(typeof(onloadfFirstTab) != "function"){
jQuery.getScript("assets/js/FirstTab.js", function(){
onloadFirstTab();
});
}
else{
onloadFirstTab();
}
} );
}
but I feel that there should be a better way.
You can't write the code entirely synchronously since you can't load script synchronously after page load ( unless you do a synchronous XHR request and eval the results - not recommended ).
You've got a couple of choices. There are pre-existing dependency management libs like RequireJS which may fit the bill here, or if you just need to load a single file you can do something like this to clean up your code a bit rather than using if/else:
function loadDependencies() {
// For the sake of example, the script adds "superplugin" to the jQuery prototype
return $.getScript( "http://mysite.com/jquery.superplugin.js" );
}
function action() {
// If superplugin hasn't been loaded yet, then load it
$.when( jQuery.fn.superplugin || loadDependencies() ).done(function() {
// Your dependencies are loaded now
});
}
This makes use of jQuery Deferreds/Promises to make the code much nicer.
If you don't want to load the JS more than once and you are going to dynamically load it, then the only way to know whether it's already loaded is to test for some condition that indicates it has already been loaded. The choices I'm aware of are:
The simplest I know of is what you are already doing (check for the existence of a function that is defined in the javascript).
You could also use a property on each tab (using jQuery's .data() that you set to true after you load the script.
You could write the dynamically loaded code so that it knows how to avoid re-initializing itself if it has already been loaded. In that case, you just load it each time, but the successive times don't do anything. Hint, you can't have any statically defined globals and you have to test if it's already been loaded before it runs its own initialization code.
(Haven't tested it yet, so I am not sure if it works, especially since I didn't yet really understand scope in javascript:)
function require(scripts, callback){
var loadCount = 0;
function done(){
loadCount -=1;
if (loadCount==0){
callback();
}
}
for ( var script in scripts){
if (!script.exitsts()){
loadCount +=1;
jQuery.getScript(script.url, done);
}
}
}
This function takes an array of scripts that are required and makes sure all of them are interpreted before it calls the callback().
The "script" class:
function script(url, testFunc){
this.url =url;
this.testFunction = testFunc;
this.exists = function(){
if(typeof(testFunction)=="function"){
return true;
}
else{
return false;
}
}
}
Where the test-function is a function that is defined (only) in the concerned script.
PS:
To enable caching in JQuery and thus prevent the browser from doing a GET request every time getScript() is called, you can use one of the methods that are presented here.
Even though unnecessary GET - requests are avoided, the script is still getting interpreted every time getScript() is called. This might sometimes be the desired behavior. But in many cases, there is no need to re-interpret library functions. In these cases it makes sense to avoid calling getScript() if the required library functions are already available. (As it is done in this example with script.exists().

Resources