I'm trying to create a robust pattern in my React Native app so that if Internet connection is not there, I won't make API calls but display a "Connection lost..." message.
I created the following util function and trying to incorporate it into my API calls using fetch but it doesn't seem to hit the then block after getting a response.
Here's the function that checks the connection:
import NetInfo from "#react-native-community/netinfo";
export const isConnectedToInternet = () => {
NetInfo.fetch().then(state => {
return state.isConnected;
});
};
And here's how I'm trying to use it:
import { isConnectedToInternet } from '../my-utils';
export const someApiCall = () => {
isConnectedToInternet()
.then(result => {
if(result) {
return (dispatch) => fetch ('https://myapi/someendpoint', fetchOptionsGet())
.then(response => {
// Process data...
})
} else {
Alert.alert('No Internet connection!');
}
})
};
Any idea why I'm not hitting the then block or suggestions to make this a robust pattern?
If you want to have the then on your isConnectedToInternet you should then make it return a promise. Try something like this:
import NetInfo from "#react-native-community/netinfo";
export const isConnectedToInternet = () => {
return new Promise((resolve, reject) => {
NetInfo.fetch().then(state => {
resolve(state.isConnected);
}).catch(e => reject(e));
})
};
In mobile software, a best practice is to always try the remote API, and then handle the exception if there's no internet. The reachability APIs provided by iOS and Android are not always accurate - they need to poll to determine connectivity. So an app is better off always trying the fetch, even if it thinks the internet may be down, in case the device got connectivity restored since the last reachability check.
If you want to model some state that shows UI when the app thinks it's offline, you can do that using the NetInfo.addEventListener() function. There's a React hook for that too I think.
Related
I have the following function that makes a GET request for my user information and caches it using react query's fetchQuery so that every call after the first one does not make a GET request and instead just pulls the data from the cache.
export const getVegetables = async () =>
await queryClient.fetchQuery(['getVegetables'], async () => {
try {
const { data } = await request.get('/vegetables');
return data;
} catch (error) {
throw new Error('Failed to fetch vegetables');
}
});
The problem is that now I actually want to make a new GET request in order to check if the user data has changed, but calling getVegetables() pulls from the cache. How can I instruct fetchQuery to make a fresh GET request and not used the cache?
A slight modification to your function will allow you to first invalidate the query (which will remove it from the cache).
export const getSelf = async (skipCache = false) => {
if(skipCache) { queryClient.invalidateQueries(['getSelf']); }
return queryClient.fetchQuery(['getSelf'], async () => {
try {
const { data } = await request.get('/users/me');
// #todo: This sideloads a bunch of stuff, that we could cache
return data;
} catch (error) {
throw new Error('Failed to fetch user information');
}
});
}
In case of using fetchQuery, you can set cacheTime to 0 in query options, so every time you call it, it will suggest that cache is outdated and fetch fresh data, but I'd suggest you to use useQuery.
Here you can read about difference between useQuery and fetchQuery
The best way is to use useQuery hook and invalidate that query.
import { useQueryClient } from '#tanstack/react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['getSelf'] })
After invalidation, it will immediately fetch fresh data.
fetchQuery will always fetch unless there is data in the cache that is considered fresh. This is determined by the staleTime setting.
staleTime defaults to 0 - which means "immediately stale". So the code you are showing that is calling fetchQuery should always fetch - unless you have a global staleTime set. You're not showing this in your code, but I guess this must be the reason. Note that fetchQuery doesn't know about staleTime being set by other observers (created by useQuery).
Now if you have a globally set staleTime and that is affecting fetchQuery, but you still want to always fetch, the best thing you can do is pass staleTime: 0 directly to fetchQuery. Again, this is the default behaviour, so it's only necessary if you have a global staleTime set:
await queryClient.fetchQuery(
['getSelf'],
async () => { ... },
{ staleTime: 0 }
)
We are unit testing a React-Native application (using Jest) which does various API calls using fetch.
We have mocked calls to fetch in our API call functions in order to test them. This works well so far. We also have functions that combine these API calls and operate some logic on them.
For example, here is one function that, given a token, will get the related user's first project (project[0]) and return the list of items from this project.
export async function getAllItems(token) {
try {
const response = await getCurrentUser(token); // fetch called inside
const responseJson = await response.json();
const allItemsResp = await getAllItemsFromSpecificProject(
token,
responseJson.projectIds[0],
); // fetch called inside
return await allItemsResp.json();
} catch (error) {
console.log(error);
return null;
}
}
Both functions getCurrentUser and getAllItemsFromSpecificProject are simple fetch calls and are currently mocked properly. Here one test that tries to test the getAllItems function:
it('Gets all items', async () => {
getAccessTokenMockFetch();
const token = await getAccessToken('usherbrooke#powertree.io', 'test!23');
getAllItemsMockFetch();
const items = await getAllItems(token.response.access_token);
expect(items.response.length).toEqual(3);
});
For clarity, here is how getAccessTokenMockFetch is done. getAllItemsMockFetch is almost identical (with different data in the response):
function getAccessTokenMockFetch() {
global.fetch = jest.fn().mockImplementation(() => {
promise = new Promise((resolve, reject) => {
resolve(accepted);
});
return promise;
});
}
where accepted contains the JSON content of a successful call. When we run this test, we get the following error:
TypeError: Cannot read property 'response' of null
And we console.log this one in the catch:
TypeError: response.json is not a function
which explains why response is null. It seems the json() call is not understood and I don't know how to mock it. I have done tons of research on Stack Overflow and beyond, but we have found nothing that helps me understand how to solve this issue. This might indicate that I am going the wrong way about this, which is quite possible since I'm new to JavaScript, React Native, and Jest.
One thing to try is giving it a fake json to call, like this:
const mockFetch = Promise.resolve({ json: () => Promise.resolve(accepted) });
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
I am trying to fetch the real-time JSON data from the server on my react app. The code that I write works rarely, continuously fail to fetch and also it creates too much request to the server which makes the browser really slow. How can I fetch the real-time data in a more efficient and less buggy way? Thanks in advance.
I used Fetch API and setInterval to request the data to the server and used react hooks to update the data. But it is doing too much request than I thought and also occurs a speed problem.
const [state, setState] = React.useState(0)
const devicePos = function () {
fetch('http://192.168.10.233:34599/')
.then(response => response.json())
.then(response => {
setState(response[0].x)
console.log(response[0].x)
})
.catch(error => {
console.log(error);
});
}
setInterval(function(){
devicePos()
}, 500);
I hope the real-time data updates faster.
instead of putting it in setInterval, asynchronous code should go in the useEffect hook. In react you need to do your async calls in lifecycle methods because they have side effects, as you were probably already experiencing. The useEffect hook is similar to componentDidMount in classes. The difference is that it comes with more in the box, meaning it handles all three lifecycle methods, componentDidMount, componentDidUpdate, componentWillUnmount
useEffect(() => {
devicePos();
}, []);
Here is the docs as to how it works
I don't think setInterval is a good way for polling data. Some responses might be slower than 500 ms and you might get older results later.
Whenever you poll, you should wait for previous response.
const [state, setState] = React.useState(0)
const [timer, setTimer] = React.useState(null)
const [isMounted, setIsMounted] = React.useState(false)
async function updateDevicePosition () {
try {
const result = await fetch('http://192.168.10.233:34599/')
const data = await result.json()
setState(data.x)
} catch (e) {
console.error(e)
}
clearTimeout(timer)
setTimer(setTimeout(updateDevicePosition, 200))
}
useEffect(() => {
if (!isMounted) {
updateDevicePosition()
setIsMounted(true)
}
})
That being said, this is a lot of requests for such job. Preferred way should be to use a socket where server is leading data and only sending messages to your client when device position is changed. Depending on when you'll use this position information, your app will be using lot's of resources
You could have it only resubmit the fetch when the response comes back (plus some delay).
const devicePos = () => {
fetch()
.then( res => res.json() )
.then( res => {
updateData();
} )
.catch( err => {
showError(err);
})
.finally( () => {
setTimeout( devicePos, 500 );
})
}
Otherwise, you might end up bogging down a server that is too slow.
The finally ensures that regardless of the api response (or timeout), the request is sent again.
I send request to server everytime user types something. I use debounce for the 400ms delay:
type = debounce((text) => {
this.props.actions.loadInfo(text)
}, 400);
When I type something, stop and start again and repeat it, several requests are send and I receive irrelevant data. I use promises:
export const loadInfo = (text) => dispatch => {
loadData(text).then(result => {
dispatch(showUserData(result));
});
};
export const loadData = async (text) => {
const tabData = await axios.get(`url&query=${text}`);
return tabData;
}
I need somehow cancel previous request if user sends the new one(when he typed something), what is the best way to do that? I expected debounce will help me but not. I use axios. This is not duplicate of questions here, I checked provided solutions but thet don't help me
The problem is similar to this one. Axios cancellation API can be used to cancel old requests. This should be done in a function that does a request (loadData) and has direct access to Axios, it may be also debounced:
let cancelObj;
export const loadData = debounce((text) => {
if (cancelObj) {
this.cancelObj.cancel();
}
cancelObj = CancelToken.source();
return axios.get(`url&query=${text}`, {
cancelToken: this._fetchDataCancellation.token
}).catch(err => {
// request wasn't cancelled
if (!axios.isCancel(err))
throw err;
});
}, 200);
Since Redux is used, other solutions may involve it, they depend on how Redux is used.
Even I tried to use debounce function in my code but the problem is that if user types very fast stop and then again start typing, in that case, your input values get updated and UI get distorted, to avoid this I used XMLHttpRequest and its abort() to cancel the previous calls, if calls do not succeed then it will be canceled,
you can try this solution, https://stackoverflow.com/a/55509957/9980970
Just wanted to know how to group all of my API calls altogether in an api.js file, in my React App (just some pseudocode would work). I have read an interesting article that introduces that idea, and I feel curious because that file structure really fits my needs. How would it be?
Moreover, the author states in a comment:
I usually just put all of my API calls into that file - they're
usually small one-or-two-line functions that call out to axios, and I
just export them.
export function login(username, password) { ... } export function
getFolders() { ... } etc.
But I feel it lacks some details to reproduce it. I am new to Javascript and React. Thanks.
Say you are using axios for http calls, I guess it would be smth like this:
api.js:
import axios from 'axios';
import { resolve } from './resolve.js';
export async function login(user, pass) {
return await resolve(axios.post('http://some-api.com/auth', { user, pass }).then(res => res.data));
}
export async function getUser(id) {
return await resolve(axios.get(`http://some-api.com/users/${id}`).then(res => res.data));
}
// and so on....
And as he said on the post, If your files starts to get too big, you can create a src/api/ and create separate files like src/api/auth.js, src/api/users.js, etc..
To resolve the promises I like to use the new async/await syntax and wrap it in a little module resolver.js:
export function async resolve(promise) {
const resolved = {
data: null,
error: null
};
try {
resolved.data = await promise;
} catch(e) {
resolved.error = e;
}
return resolved;
}
And your component smth like:
// ...
componentDidMount() {
this.getUser();
}
async getUser() {
const user = await api.getUser(this.props.id);
if(user.error)
this.setState({ error: user.error });
else
this.setState({ user: user.data });
}
Again, this is something I like to do, I think the code looks clearer, keeping a synchronous structure. But I guess it's perfectly fine to resolve your promises with .then() and .catch() also.
I hope that helped!
It depends on how much API functions a project has.
I usually stick with project structure called "grouping by file type" mentioned in React official website and keep API related files in a separate directory where every file has an API functionality dedicated to a specific entity.
However, for small projects, it makes sense to keep all API functionality in one file.