Right way of fetching data - React - javascript

I got Users and Places as two different databases where each user has may places.
I have BasicInfo component where at a time 1 place of the user has to be loaded.
ComponentWillMount of BasicInfo
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchPlaces(1))
}
I need to pass user id in fetchPlaces, as of now I'm hardcoding, but how do I do with the results of fetchUser? Should I do in componentWillReceiveProps(nextProps) ? Or is there any other way of doing it?
componenetWillReceiveProps makes sense, but I was wondering what if it is a chain of events? May be depending on place id if I have to fetch some other data.
Actions :
export function fetchPlaces(user_id) {
return function(dispatch) {
axios.get("/getPlaces?user_id=" + user_id)
.then((response) => {
console.log(response);
dispatch({type: "FETCH_PLACES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACES_REJECTED", payload: err})
})
}
}
export function fetchPlace(place_id) {
return function(dispatch) {
axios.get("/getPlace?place_id=" + place_id)
.then((response) => {
console.log(response);
dispatch({type: "FETCH_PLACES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACES_REJECTED", payload: err})
})
}
}

It looks like you're already using redux-thunk? If so, you can return the promise from your axios.get() calls:
return axios.get(...);
...then you can do something like this (I'm guessing what your fetchUser and user might look like):
this.props.dispatch(fetchUser()).then(user => this.props.dispatch(fetchPlaces(user.id)))

You can use a thunk or a promise middleware. You can find the motivation and examples in the documentation.

Related

Does .then wait for setState to finish before calling the next method?

I have this method in my React app:
My issue is that the createQuestions() runs before the findEmployeeId() method is done. Shouldn't the .then make it wait?
Inside the findEmployeeId() it is doing a setState operation. Does it not wait for that to finish? I need that data update before the createQuestions() runs.
createInterview() {
fetch(API_URL + `/interview/create`, {
method: "PUT",
body: JSON.stringify({
employee: this.state.employee,
employment_level: this.state.employment_level,
audit_id: this.props.auditId,
}),
headers: { "Content-Type": "application/json" },
})
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.catch((err) => console.log(err))
.then(() => this.findEmployeeId())
.then(() => this.createQuestions());
this.setState({ showHide: false });
}
This is the findEmployeeId method. How can I make it wait until completion before the next one runs?
findEmployeeId() {
fetch(API_URL + `/employee/${this.state.employee}/${this.props.auditId}`)
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.then((result) => {
this.setState({ lastEmployeeId: result[0].employee_id });
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
#raina77ow wrote it in his comment already, but to state it a little more clearly, I think you need to return the promise from findEmployeeId():
findEmployeeId() {
/* Note the return here */
return fetch(API_URL + `/employee/${this.state.employee}/${this.props.auditId}`)
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.then((result) => {
this.setState({ lastEmployeeId: result[0].employee_id });
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
The question is basically finding a way to guarantee to have createQuestions() executed after findEmployeeId(). This is the heart of the question. The use of the setState Callback can make this to happen. It is said that the use of the setState call back could be cumbersome but if there is a good understanding about composition of two functions then this should not be.
setState is asynchronous... so it is important to not make assumptions on sequentiality for two ops like:
this.setState({stateA : a})
this.setState({stateB : b})
The only way to "force" sequentiality through setState is through its call back:
this.setState({ .... }, () => {do after setState is done})
Somehow this is what you need to follow i.e. at the setState you are using inside findEmployeID it is necessary to do something like:
const self = this;
this.setState({ ..... }, () => {self.createQuestions()})
Whoever placed that negative better explain his reason. The .then part of the fetch does not guaranty sequentiality so it falls to the situation similar where two setStates are executed one after the other. A wait also does not guaranty sequentiality.

How to make a Javascript/React/Typescript fetch call asynchronous?

Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).

React-Redux - using a response from a fetch request in componentDidMount in another request in the component

I have a component that fires off a fetch request (in redux) on componentDidMount. In the same component I need to fire off another redux fetch request using the response data from the first one, preferably before the render.
Due to the fetch being executed in the redux action I havent been able to do this with a promise in componentDidMount as the promise resolves when the action starts and not finishes.
on research I thought it might be able to do it with componentWillRecieveProps however I don't fully understand how and also read that this hook is being depreciated soon.
any help would be greatly appreciated.
first action:
componentDidMount(){
this.props.onGetSessionId(this.parseToken(this.props.location.search))
};
secondAction:
this.props.onFetchFavourites(this.props.sessionId)
It's hard to help here without seeing the code for how onGetSessionId and onFetchFavourites are implemented but assuming that you're using redux-thunk, it's probably something like this:
function getSessionId(token) {
return function (dispatch) {
return fetchSessionId(token)
.then(
response => dispatch(getSessionIdSuccess(forPerson, response.id))
)
.catch(
error => dispatch(getSessionIdFailure(error)
)
};
}
function getFavorites(sessionId) {
return function (dispatch) {
return fetchFavorites(sessionId)
.then(
response => dispatch(getFavoritesSuccess(sessionId, response.favorites))
)
.catch(
error => dispatch(getFavoritesFailure(error)
)
};
}
fetch returns a promise so you can just keep chaining .then and using the return value from the previous then. this lets you do something like
function getSessionIdAndFavorites(token) {
return function (dispatch) {
return fetchSessionId()
.then(
response => {
dispatch(getSessionIdSuccess(response.id))
return response.id
}
)
.then(id => {dispatch(getFavorites(id)}
.catch(
error => dispatch(getSessionAndFavoritesIdFailure(error)
)
};
}
In onGetSessionId you can return promise, like this:
function onGetSessionId(param) {
return new Promise((resolve, reject) => {
apiClient.call()
.then(data => {
resolve(data);
})
.catch(error => {
reject(error)
})
})
}
and then in componentDidMount:
componentDidMount(){
this.props.onGetSessionId(this.parseToken(this.props.location.search))
.then(data => {
// ... do stuff with returned data
})
}

Countless http requests created in MERN stack application

I don't know what does cause this, it sends new request almost every half a second. I was thinking it's because I call my action in render method but it's not, tried to call it in componentDidMount, the same result.
Here is the code:
Action:
export const getComments = () => dispatch => {
dispatch({
type: GET_COMMENTS
})
fetch(`${API_URL}/comments`,
{ method: 'GET', headers: {
'content-type': 'application/json'
}})
.then((res) => res.json())
.then((data) => dispatch({
type: GET_COMMENTS_SUCCESS,
payload: data
}))
.catch((err) => dispatch({
type: GET_COMMENTS_FAILED,
payload: err
}))
}
Since I need post id loaded before I call the comment action I put it in render method:
componentDidMount() {
const { match: { params }, post} = this.props
this.props.getPost(params.id);
}
render() {
const { post, comments } = this.props;
{post && this.props.getComments()}
return <div>
...
Here is the route:
router.get("/comments", (req, res) => {
Comment.find({})
.populate("author")
.exec((err, comments) => {
if (err) throw err;
else {
res.json(comments);
}
});
});
Your getComments() function is running during render. The dispatch used in the action is causing a re-render, causing getComments() to fire again, producing an infinite loop.
Instead of fetching comments in the render() function, you should instead fetch them in the componentDidMount lifecycle hook, then in the render function simply display the comments from props;
getComments() is invoking the http request, so it should be moved to componentDidMount lifecycle hoook.
This should work:
componentDidMount() {
const { match: { params } = this.props
this.props.getPost(params.id);
this.props.getComments()
}
render() {
const { post, comments } = this.props;
{post && comments}
return <div>
...
When the component has mounted, the params are retrieved from props.match and the Post and Comments are fetched. Then with redux, post and comments data is dispatched, and can be accessed in the connected component's render method.

Handle async requests in react native / redux

I'm using firebase to build an app with react native. I'm struggling with how I should structure my redux actions within my app.
As is stands I have a function that will fetch a users profile. I make a reference to the firebase firestore and then use then and catch statements to resolve the promise returned. Here is where my problem comes into it.
return(dispatch){
dispatch({type: GET_PROFILE})
var docRef = firebase.firestore().doc('users/' + userUid);
docRef.get()
.then(() => {
dispatch({type: GET_PROFILE_SUCCESS})
})
.catch(() => {
dispatch({type: GET_PROFILE_FAIL})
})
}
I am dispatching the action GET_PROFILE so that when my render function is called in my profile screen, it will load a spinner while the user waits.
The problem I've got is that if the user isn't connected to the internet. When they make the request, the action is sent to redux but GET PROFILE SUCCESS / FAIL is never dispatched, as having no internet connection isn't an error
As a result all the user sees is a spinner until they restart the app. I basically want to alert the user that they need a connection to the internet.
I'm new to react native, Could someone advise me what's the best way to do this in react native?
Thanks
You can get network informations with NetInfo and store it in redux :
In your main component :
componentDidMount() {
NetInfo.isConnected.fetch().then((isConnected) => {
store.dispatch({ type: 'CHANGE_CONNECTION_STATUS', isConnected });
});
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
handleConnectionChange = (isConnected: boolean) => {
store.dispatch({ type: 'CHANGE_CONNECTION_STATUS', isConnected }));
}
Then you can wrap your request with an if statement to handle offline use cases :
return(dispatch){
if(store.getState().isConnected) {
dispatch({type: GET_PROFILE})
var docRef = firebase.firestore().doc('users/' + userUid);
docRef.get()
.then(() => {
dispatch({type: GET_PROFILE_SUCCESS})
})
.catch(() => {
dispatch({type: GET_PROFILE_FAIL})
})
} else {
Alert.alert('You are offline');
}
}
You can use this piece of code for network fail condition
return(dispatch){
dispatch({type: GET_PROFILE})
var docRef = firebase.firestore().doc('users/' + userUid);
docRef.get()
.then(() => {
dispatch({type: GET_PROFILE_SUCCESS})
},
// ADD THESE LINES
(error) => {
dispatch({type: GET_PROFILE_FAIL})
})
.catch(() => {
dispatch({type: GET_PROFILE_FAIL})
})
}

Categories