I am trying to understand how developers use Promise with React-Native. It would be great to get feedback and recommendations on how to setup API calls and handle the data. Please understand I never used Promise before and that I am new to React-Native.
Thank you in advance. Any resource about this subject is welcome too.
Pseudocode
Child
Retrieve two variables
Use these two variables to build an URL
Trigger the first Promise and resolve
Retrieve another two variables
Use these two variables to build a new an URL
Trigger the second Promise and resolve
Gather the data from both promises and pass to parent
Parent
Retrieve data from Child
Get data from the first Promise and set to a state
Get data from the second Promise and set to another state
APIservice.js
Child
Is it a good practice to setup all your API calls in a separate file? It's likely that in the future I will need to make different API calls, would you create multiple functions to handle that?
class APIservice {
_getStopPoint = (endpoint) => {
return new Promise(function(resolve, reject) {
fetch(endpoint)
.then((response) => response.json())
.then((data) => {
console.log("APIservice StopPoint", data)
resolve(data);
});
});
};
};
module.exports = new APIservice
App.js
Parent
As you can see, the way I setup the endpoint is lame. It's not ideal as the URL is the same. I want to structure something that can receive two variables and build the URL on the go. Something like https://api.tfl.gov.uk/Line/${routeid}/Arrivals/${stationid}.
If I manage that, how can I pass the API call to the APIservice having only one endpoint that dynamically will change based on the two variables it receives? I am not sure how to differentiate the call in the Promise.all having only "one" URL.
That brings me another issue. When setting the state in App.js, should I setState using the specifics array from data? Something like bus: data[0], tube: data[1]. Is this a good practice?
let APIservice = require('./APIservice')
let endpoint = 'https://api.tfl.gov.uk/Line/55/Arrivals/490004936E'
let endpoint1 = 'https://api.tfl.gov.uk/Line/Northern/Arrivals/940GZZLUODS'
let loadData = (endPoint) => {
// Multiple API calls
Promise.all([
APIservice._getStopPoint(endpoint),
APIservice._getStopPoint(endpoint1),
])
.then((data) => {
console.log("App.js", data)
})
.catch((error) => {
console.log(error)
})
}
export default class App extends Component {
componentWillMount() {
// URL fetch based on variables, not dynamic
loadData(endpoint)
loadData(endpoint1)
}
render() {
loadData("hello")
return (
<View style={styles.container}>
<Text>
Promise
</Text>
</View>
);
}
}
you can try this example
const callbackFn = (firstName, callback) => {
setTimeout(() => {
if (!firstName) return callback(new Error('no first name
passed in!'))
const fullName = `${firstName} Doe`
return callback(fullName)
}, 2000)
}
callbackFn('John', console.log)
callbackFn(null, console.log)
Related
I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>
I noticed that some developers are using so-called "services" to manage front end requests, for example:
const httpService = {
get(url) {
fetch(url).then((response) => {
if (!response.ok) {
throw new Error('error')
}
return response.json()
})
}
}
const getPostService = (url) => { // this is the service
return httpService.get(url).then((json) => json)
}
getPostService('/post')
.then(r => setData(r))
.catch(e => setError(e));
Is there a reason to create multiple services like: getPostService or getUserService, or getDataService, or the implementation is the same if inside the code will do:
httpService.get(url).then((json) => setData(json)).then(e => setErr(e))
Is a reason to create services or they are redundant in my case being enough to fetch data only using httpService without creating many services in my app?
Your example is strange, and doesn't make a ton of sense to me.
It doesn't look like your getPostService returns any service, it literally just wraps the httpService.get function, with no real benefit.
You can see this even clearer when cleaning up the function from:
const getPostService = (url) => { // this is the service
return httpService.get(url).then((json) => json)
}
To:
const getPostService = url => httpService.get(url);
However, if this function was called getPosts or it returned a useful object that does operations related to posts, it makes a lot of sense.
For example, maybe your PostService kinda looks like this:
class PostService {
getPosts() {}
updatePost() {}
deletePost() { }
}
Now your service has some methods that have useful behavior and that can be re-used. If multiple of your components need a 'list of posts', and if all those components just do HTTP requests, then you need to change all components if you want to change something about how 'lists of posts' are fetched.
Putting this in a central place increases maintainability
I'm working on IPC in NodeJS and want to be able to send a message to the parent process from the child and "wait" for the result. My idea was to keep track of all the send messages in a map that maps the unique message ID to a promise. Once the process.on('message`) has been called I lookup the promise by the ID I got back from the parent and want to resolve or reject the promise.
I came up with this, but am stuck at the resolve/reject part:
'use strict'
import RequestMessage from "../messages/request/RequestMessage";
import ResponseMessage from "../messages/response/ResponseMessage";
const process = require('process');
export class CommunicationManager {
private messageQueue: Map<string, Promise<any>>;
constructor() {
this.messageQueue = new Map();
process.on('message', (payload: any) => {
if (payload.hasOwnProperty("_id")
&& this.messageQueue.has(payload.get("_id"))) {
let promise = this.messageQueue.get(payload);
// Resolve or reject the promise..
this.messageQueue.delete(payload.get("_id"));
} else {
console.error(`Got unknown message from parent: ${payload}`);
}
});
}
public execute(message: RequestMessage): Promise<ResponseMessage> {
process.send(message);
this.messageQueue.set(message.id(), // a promise here);
}
}
Can someone push me in the right direction on how to solve this? Is this even possible and best-practice?
Thanks!
You would not store the promise in the map. You would store only the resolver function to call later - the promise is created and returned immediately.
init() {
process.on('message', (payload: any) => {
if ("_id" in payload && this.messageQueue.has(payload._id)) {
const resolve = this.messageQueue.get(payload._id);
this.messageQueue.delete(payload._id);
if (payload.isFulfilled) {
resolve(payload.value);
else {
resolve(Promise.reject(payload.error));
}
} else {
console.error(`Got unknown message from parent: ${payload}`);
}
});
}
public execute(message: RequestMessage): Promise<ResponseMessage> {
return new Promise(resolve => {
this.messageQueue.set(message.id(), resolve);
process.send(message);
});
}
It is rare to call resolve in some other scope than the promise executor's, but messaging is one of those cases where it is necessary and the standard practice. Btw, you might want to consider putting a timeout on the response receival.
#Bergi had a nice answer. I have a follow-up to anyone considering doing something like this: it's a concept called "Deferred/Deferable" that was hot for a while but fell out of favor over async/await - check this guide https://codingbeautydev.com/blog/javascript-resolve-promise-from-outside/ or even this library https://www.npmjs.com/package/deferred
You would then store a deferable in your Map, return deferred.promise in your execute() and call deffered.resolve() whenever you want to actually resolve it.
I have an issue where I am trying to use the Redux state to halt the execution of some polling by using the state in an if conditional. I have gone through posts of SO and blogs but none deal with my issue, unfortunately. I have checked that I am using mapStateToProps correctly, I update state immutably, and I am using Redux-Thunk for async actions. Some posts I have looked at are:
Component not receiving new props
React componentDidUpdate not receiving latest props
Redux store updates successfully, but component's mapStateToProps receiving old state
I was kindly helped with the polling methodology in this post:Incorporating async actions, promise.then() and recursive setTimeout whilst avoiding "deferred antipattern" but I wanted to use the redux-state as a single source of truth, but perhaps this is not possible in my use-case.
I have trimmed down the code for readability of the actual issue to only include relevant aspects as I have a large amount of code. I am happy to post it all but wanted to keep the question as lean as possible.
Loader.js
import { connect } from 'react-redux';
import { delay } from '../../shared/utility'
import * as actions from '../../store/actions/index';
const Loader = (props) => {
const pollDatabase = (jobId, pollFunction) => {
return delay(5000)
.then(pollFunction(jobId))
.catch(err => console.log("Failed in pollDatabase function. Error: ", err))
};
const pollUntilComplete = (jobId, pollFunction) => {
return pollDatabase(jobId, pollFunction)
.then(res => {
console.log(props.loadJobCompletionStatus) // <- always null
if (!props.loadJobCompletionStatus) { <-- This is always null which is the initial state in reducer
return pollUntilComplete(jobId, pollFunction);
}
})
.catch(err=>console.log("Failed in pollUntilComplete. Error: ", err));
};
const uploadHandler = () => {
...
const transferPromise = apiCall1() // Names changed to reduce code
.then(res=> {
return axios.post(api2url, res.data.id);
})
.then(postResponse=> {
return axios.put(api3url, file)
.then(()=>{
return instance.post(api3url, postResponse.data)
})
})
transferDataPromise.then((res) => {
return pollUntilComplete(res.data.job_id,
props.checkLoadTaskStatus)
})
.then(res => console.log("Task complete: ", res))
.catch(err => console.log("An error occurred: ", err))
}
return ( ...); //
const mapStateToProps = state => {
return {
datasets: state.datasets,
loadJobCompletionStatus: state.loadJobCompletionStatus,
loadJobErrorStatus: state.loadJobErrorStatus,
loadJobIsPolling: state.loadJobPollingFirestore
}
}
const mapDispatchToProps = dispatch => {
return {
checkLoadTaskStatus: (jobId) =>
dispatch(actions.loadTaskStatusInit(jobId))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DataLoader);
delay.js
export const delay = (millis) => {
return new Promise((resolve) => setTimeout(resolve, millis));
}
actions.js
...
export const loadTaskStatusInit = (jobId) => {
return dispatch => {
dispatch(loadTaskStatusStart()); //
const docRef = firestore.collection('coll').doc(jobId)
return docRef.get()
.then(jobData=>{
const completionStatus = jobData.data().complete;
const errorStatus = jobData.data().error;
dispatch(loadTaskStatusSuccess(completionStatus, errorStatus))
},
error => {
dispatch(loadTaskStatusFail(error));
})
};
}
It seems that when I console log the value of props.loadJobCompletionStatus is always null, which is the initial state of in my reducer. Using Redux-dev tools I see that the state does indeed update and all actions take place as I expected.
I initially had placed the props.loadJobCompletionStatus as an argument to pollDatabase and thought I had perhaps created a closure, and so I removed the arguments in the function definition so that the function would fetch the results from the "upper" levels of scope, hoping it would fetch the latest Redux state. I am unsure as to why I am left with a stale version of the state. This causes my if statement to always execute and thus I have infinite polling of the database.
Can anybody point out what might be causing this?
Thanks
I'm pretty sure this is because you are defining a closure in a function component, and thus the closure is capturing a reference to the existing props at the time the closure was defined. See Dan Abramov's extensive post "The Complete Guide to useEffect" to better understand how closures and function components relate to each other.
As alternatives, you could move the polling logic out of the component and execute it in a thunk (where it has access to getState()), or use the useRef() hook to have a mutable value that could be accessed over time (and potentially use a useEffect() to store the latest props value in that ref after each re-render). There are probably existing hooks available that would do something similar to that useRef() approach as well.
I am creating an application in which I use redux and node-fetch for remote data fetching.
I want to test the fact that I am well calling the fetch function with a good parameter.
This way, I am using jest.mock and jasmine.createSpy methods :
it('should have called the fetch method with URL constant', () => {
const spy = jasmine.createSpy('nodeFetch');
spy.and.callFake(() => new Promise(resolve => resolve('null')));
const mock = jest.mock('node-fetch', spy);
const slug = 'slug';
actionHandler[FETCH_REMOTE](slug);
expect(spy).toHaveBeenCalledWith(Constants.URL + slug);
});
Here's the function that I m trying to test :
[FETCH_REMOTE]: slug => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
await fetch(Constants.URL + slug);
addLocal();
};
}
AS you can see, I am trying to log the console.log(fetch()) behavior, and I am having the default promise to resolve given by node-fetch, and not the that I've mock with Jest and spied with jasmine.
Do you have an idea what it doesn't work ?
EDIT : My test displayed me an error like my spy has never been called
Your action-handler is actually a action handler factory. In actionHandler[FETCH_REMOTE], you are creating a new function. The returned function taskes dispatch as a parameter and invokes the code you are showing.
This means that your test code will never call any function on the spy, as the created function is never invoked.
I think you will need to create a mock dispatch function and do something like this:
let dispatchMock = jest.fn(); // create a mock function
actionHandler[FETCH_REMOTE](slug)(dispatchMock);
EDIT:
To me, your actionHandler looks more like an actionCreator, as it is usually called in redux terms, though I personally prefer to call them actionFactories because that is what they are: Factories that create actions.
As you are using thunks(?) your actionCreater (which is misleadingly named actionHandler) does not directly create an action but another function which is invoked as soon as the action is dispatched. For comparison, a regular actionCreator looks like this:
updateFilter: (filter) => ({type: actionNames.UPDATE_FILTER, payload: {filter: filter}}),
A actionHandler on the other hand reacts to actions being dispatched and evaluates their payload.
Here is what I would do in your case:
Create a new object called actionFactories like this:
const actionFactories = {
fetchRemote(slug): (slug) => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
let response = await fetch(Constants.URL + slug);
var responseAction;
if (/* determine success of response */) {
responseAction = actionFactories.fetchSuccessful(response);
} else {
responseAction = actionFactories.fetchFailed();
}
dispatch(responseAction);
};
}
fetchFailed(): () => ({type: FETCH_FAILED, }),
fetchSuccessful(response): () => ({type: FETCH_FAILED, payload: response })
};
Create an actionHandler for FETCH_FAILED and FETCH_SUCCESSFUL to update the store based on the response.
BTW: Your console.log statement does not make much sense too me, since fetch just returns a promise.