Redux Thunk vs Making api call in react component - react-native

I was wondering if what I've been doing in my ReactNative/Redux application is wrong. This is how I've been handling async actions.
MyComponent.js
componentDidMount() {
fetch('https://www.mywebsite.com')
.then(data => this.props.handleApiSuccess(data)) // injected as props by Redux
.catch(err => this.props.handleApiError(err)); // injected as props by Redux
}
The redux-thunk way I should probably be doing
export const handleApiCall = () => dispatch => {
fetch('https://www.mywebsite.com')
.then(data => dispatch(handleApiSuccess(data)))
.catch(err => dispatch(handleApiError(err)));
}
Is there anything wrong with the way its being done in the first part?

There is nothing wrong in terms of bugs: the code will work and serve its purpose.
But in terms of design it has huge flaw: coupling. Merging your fetching logic inside the Components may cause complications as your app will grow.
What if your way of fetching will change, e.g. you'll decide to communicate with server via websockets? What if your way of handling of server response will change, i.e. handleApiError will have to be replaced with something else? What if you'll decide to reuse same fetching in the different part of your app?
In all these cases you'll have to alter your existing Components which ideally shouldn't be affected by such logic changes.

Related

How to use useEffect() without performance degradation

I have a React Native app where, on one screen, I have a useEffect hook that constantly fetches data from a site and updates my local variable with that data (see below for a simplified example). The problem I am running into though is that this is killing the performance of my app. Even if you leave this screen, the app is sluggish.
The issue is no doubt caused by the countless calls to the URL to get the data & constantly resetting my local variable with the data. I tried using a dependency array with the hook, but if I do that it doesn't continually update, it only updates on the first load of the screen and I need it to update whenever there is a change (whenever new data is available).
Is there a way to do this so that I constantly get any updates from the remote source but don't slow down my app?
const [retrievedData, setRetrievedData] = [{}];
useEffect(() => {
let fetchedData;
fetch(
'https://site-with-data.com',
)
.then(response => response.json())
.then(json => {
setRetrievedData(processData(json.events));
})
.catch(error => console.error(error));
});
When useEffect doesn't have a dependencies array (the second argument), it will run on every render. That is why you are making so many calls.
If you need to update the data real-time, the first question you must answer is: how will the client know the data has changed in the server?
One way you could make the server notify the client is by using WebSockets. A WebSocket creates a bidirectional connection between the client and the server, and the server can notify the client whenever anything changes. Other than that, you could also use a technique called "long polling" or "server-sent events".
But any of these three solutions would require you to change your server (in addition to your client).
Quick fix: limit the update frequency to "refresh every N seconds"
The quick and dirty alternative without changing the server is just to decide a frequency (e.g., update every 5s) and go with it in the client.
Example:
useEffect(() => {
const intervalId = setInterval(() => {
fetch(
'https://site-with-data.com',
)
.then(response => response.json())
.then(json => {
setRetrievedData(processData(json.events));
})
.catch(error => console.error(error));
}, 5000); // update every 5s
return () => clearInterval(intervalId);
}, []); // add to this array any variable that affects the fetch (e.g., URL)
Which number of seconds should you use? That will depend on each case. To decide that, evaluate both UX and server load.

How do MongoDB Stitch SDK's work in regards to client instantiation or how does Stitch.defaultAppClient.getServiceClient work?

I'm using expo to build out a React Native application and I'm running into issues when attempting to write code that accesses remote MongoDB servers. I'm attempting to use MongoDB's provided Stitch SDK's for React Native.
When running
const mongoClient = Stitch.defaultAppClient.getServiceClient(RemoteMongoClient.factory, "mongodb-atlas");
I'm running into the following error:StitchServiceError: service not found: 'mongodb-atlas'
When my app initializes in my main App component, I'm initializing the default client using Stitch.initializeDefaultAppClient per the recommended documentation. Based on my debugging logs, this part is working correctly and I'm able to authenticate with the service correctly and I am storing the client in the App component's state. I'm running the loadClient method in the constructor of my main App component.
_loadClient() {
console.log("Loading Stitch client");
Stitch.initializeDefaultAppClient("xxxxxxxxxxxxxxx").then(client => {
this.setState({ client });
this.state.client.auth
.loginWithCredential(new AnonymousCredential())
.then(user => {
console.log(`Successfully logged in as user ${user.id}`);
this.setState({ currentUserId: user.id });
this.setState({ currentUserId: client.auth.user.id });
})
.catch(err => {
console.log(`Failed to log in anonymously: ${err}`);
this.setState({ currentUserId: undefined });
});
});
}
For more context: I'm executing the getServiceClient function in a separate react saga so that I can fetch data behind the scenes based on actions that are dispatched within the application. I'm calling getServiceClient inside a function that gets called upon every dispatch of a specific action. All of this is exported to a single async function which is then applied as saga middleware enhancer to a store.
I think I'm not able to retrieve the service client because the defaultappclient isn't initialized within the context of the saga because of the way sagas work (from my understanding) but I need more insight into how getServiceClient() works.
I ended up storing the client in a local instance in the saga js file so that the instance is available for all sagas and I plan on keeping all sagas within this file. I am using asynchronous functions to ensure that the app client is initialized prior to binding any client requests to redux actions.
Example:
let appClient;
function* initAppClient() {
console.log("Initializing Stitch Client");
yield Stitch.initializeDefaultAppClient("client-identification-here ").then(client => appClient=client);
}
export default function* rootSaga() {
yield initAppClient();
yield takeEvery('ACTION HERE', uploadState);
}
The downside to this approach is that this instance won't be available to the rest of my react application and I won't be able to use Stitch functionality to update anything through the actual react application. This works for me as I only plan on using Stitch when state changes within my application and this decouples any server/remote data operations from react application functionality which focuses on presentation, routing, etc. If I want to use Stitch within my react application, I would have to initialize another client within react's context.

Redux-Persist with React-Native-Background-Fetch

I am creating a React-Native app that fetches data from an API as a background service.
I have looked around the web if I can manually rehydrate the store with the data fetched during the background task, but I could not find anything.
Is it possible to rehydrate the redux-persist store manually while the app is killed, from a background 'service' task?
For the people still wondering, if it is possible to use react-native-background-fetch for scheduling ANY task, it is completely fine as long as it does not touch the UI eg. (AsyncStorage, Redux-Persist, Realm, DB...) is not directly related to invoking change in the UI, so it is completely fine to use.
In my particular case, I am using the slowest option - AsyncStorage - to persist a props sort of object which I use on global App level and pass derived data onto my components:
// Example of HeadlessTask implementation
import BackgroundFetch from 'react-native-background-fetch'
import AsyncStorage from '#react-native-community/async-storage';
const HeadlessTask = async () => {
// Prepare data - fetching from API, other transformations...
let propsObject = {};
AsyncStorage.setItem(ITEM_KEY, JSON.strigify(propsObject))
.then(() => {
console.log('[AsyncStorage] Object Saved!');
// For iOS specifically we need to tell when the service job
// is done.
BackgroundFetch.finish();
})
.catch((e) => {
console.log('[AsyncStorage] Error saving object: ', e);
BackgroundFetch.finish();
});
}
P.S. See https://github.com/transistorsoft/react-native-background-fetch to see how to install and implement Background Fetch.

Vue - Do API calls belong in Vuex?

I am struggling with finding answer for where to ideally put API calls in vue modules. I am not building an SPA. For example my auth block has several components for login, password reset, account verifiction etc. Each block uses axios for API calls. Axios already provides promises, which are async.
The question is about the best pracitces. Do API calls belong in a Vuex actions? Are there any pros/cons of such approach?
Is there any drawback of keeping axios calls within the components they belong to?
I do API calls in services, not Vuex or components. Basically, mixing the API calls in with the store code is a bit too multi-responsibility, and components should be about providing for the view not fetching data.
As an example of a simple service (using Vue.http but same for an Axios call),
FileService .js
import Vue from 'vue'
export default {
getFileList () {
return Vue.http.get('filelist.txt')
.then(response => {
// massage the response here
return filelist;
})
.catch(err => console.error('getFileList() failed', err) )
},
}
I use it in another service as below (the number of layers is up to you).
Note, the outer service is checking the store to see if the fetch already happened.
DataService.js
import FileService from './file.service'
checkFiles (page) {
const files = store.state.pages.files[page]
if (!files || !files.length) {
return store.dispatch('waitForFetch', {
resource: 'files/' + page,
fetch: () => FileService.getFileList(),
})
} else {
return Promise.resolve() // eslint-disable-line no-undef
}
},
waitForFetch is an action that invokes the fetch function passed in to it (as provided by FileService). It basically provides wrapper services to the fetch, like timeout and dispatching success and failure actions depending on the outcome.
The component never knows about the API result (although it may initiate it), it just waits on data to appear in the store.
As for drawback of just calling the API in the component, it depends on testability, app complexity. and team size.
Testability - can mock out a service in unit tests.
App complexity - can handle timeout / success / failure orthogonally to the API call.
Team size - bigger teams, dividing up the task into smaller bites.

Async store enhancer?

I'm trying to build a version of https://github.com/elgerlambert/redux-localstorage for React Native. The catch is that the React Native version of localStorage — AsyncStorage — has an asynchronous API. Unless I'm missing something, this effectively means that I cannot return the enhanced store with the existing store enhancer API.
Is there a workaround for this, or is it a fundamental problem in the store enhancer API?
Why is it important for the persistence operation to complete synchronously? I would argue that for performance reasons, you explicitly do not want the IO operation to delay the processing of the action within the Redux cycle.
Depending on your application, it might make sense to not only persist your store state asynchronously, but even debounce to persistence further to allow the user interaction and the following rerender to complete before persisting the new state.
Here is a simplified version of how I have implemented lazy persistence in my apps:
import {debounce} from 'lodash';
/**
* Redux middleware for persisting store state on change
*/
export default function takeStateSnapshot(store) {
// wait for input to stop for a while to avoid unnecessary
// serialization and IO overhead during busy dispatches
const takeLazySnapshot = debounce(saveSnapshot, 250);
return next => action => {
const result = next(action);
takeLazySnapshot(store.getState());
return result;
};
}
async function saveSnapshot(state) {
try {
await AsyncStorage.setItem('my-app-state', JSON.stringify(state));
} catch (e) {
console.error('Error persisting application state', e);
}
}
This approach does not give you guarantees that the latest state will be persisted in case of abrupt app shutdowns, but in our case it does not matter, as the store app state is used as a cache and never trusted as a source of truth.