How to change fetch call and rerender screen? - javascript

I have a React Native App that is communicating with a PostgresDB threw a ExpressJS REST API.
I have a fetchcall that gives back some data that I want to display inside my app. It calls the route with two params. One being the userid that I stored inside AsyncStorage and the other one being the selected Date that is stored in my state.
My problem is that the fetch url should be updated when the date state changes and then give back the data for the given day. Currently it is not rerendering the screen but the date changes. Is there a specific way how to tell my fetch query that it should rerender?
For example: `http://myip/api/hours/daydata/${realDate}/${userid}`
is my fetchurl and realDate is this.state.date and userid is the AsyncStorage stored userid.
`http://myip/api/hours/daydata/2019-11-06/138` this url gives me back the data I need for the given day and for the user.
On a Button click my date state changes for example to 2019-11-07.
`http://myip/api/hours/daydata/2019-11-07/138` this would be the new url that should be fetched next and I expect to rerender the screen to see the changes. But it doesnt rerender!
Here you have some code:
async fetchData() {
let realDate = this.state.date;
await getData("userid") // asyncStorage gets userid correctly
.then(data => data)
.then(value => this.setState({ userid: value }))
.catch(err => console.log("AsyncS_Error: " + err));
const userid = this.state.userid;
console.log("cpmDM id_ " + userid);
await fetch(
`http://myip/api/hours/daydata/${realDate}/${userid}`
)
.then(console.log("realD8_: " + realDate))
.then(res => res.json())
// .then(res => console.log(res))
.then(res => {
console.log(res[0].remark);
return this.setState({
remark: res[0].remark
});
})
.catch(err => console.log(err));
}
componentDidMount() {
this.fetchData();
}
I expect to get a rerender because I am calling setState on remark.
In the first render the remark from the first selected date gets diplayed but as I change the day the remark stays and doesnt update.
Ideas:
Is there a way to log the current fetchurl? Do I need something like componentWillUpdate maybe?
EDIT: This is the way that my state gets updated:
dateForwardHandler = () => {
console.log("Current Date:", this.state.date);
const newDate = Moment(this.state.date).add(1, "d").format("YYYY-MM-DD");
this.setState({ date: newDate.toString() });
console.log("Current Date:", this.state.date);
};
EDIT 2.0:
calling fetchData() inside the dateBackHandler and dateForwardHandler is actually showing me the different remarks where I need them but somehow they are 1 day off. If I go back in date for ex to the 25.10 I get the remark from the 26.10 and when I then go to 24.10 I get the one from 25.10
Seems like the component does not get updated on the first press but on the second so its one day off.
EDITS MERGED: Soo thanks for all the help that you guys gave me.
I want to tell you how the app is behaving currently.
dateForwardHandler = () => {
console.log("Current Date:", this.state.date);
const newDate = Moment(this.state.date).add(1, "d").format("YYYY-MM-DD");
this.setState({ date: newDate.toString() });
console.log("Current Date:", this.state.date);
this.fetchData(); // THIS CALL IS NEW
};
By calling this.fetchData(); after the state changes and on Button Press I get some different remarks shown but a Problem I have is that it is always one day off. So somehow the frontend and the fetchcall are not updating at the same time.
But I think it is right to first set the state.date to the new date and then to call fetchData...
Thanks for all the help so far ;)

You don't have to return this.setState, so instead of
return this.setState({
remark: res[0].remark
});
use
this.setState({
remark: res[0].remark
});

Why do you need the current date in your state and update it everytime when needed? cannot you just do:
let currentDate = Moment().add(1, "d").format("YYYY-MM-DD").toString()
This way you don't force a re-render everytime you update the date, while still fetching with the date in the moment you are fetching

First of all I would not put two awaits into one single async function. It could be the your userId will not be retrieved in time for your fetch method.
Regarding the rerendering, try to first return the result of fetchData (res.json) and then setState outside of the async function.
EDIT:
As you mentioned that you wanted to do on button click, it could look somewhat like this:
fetchData = (url, method) => {
let error;
return fetch(url, {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}).then((response) => {
if (response.ok) {
return response.json().then(response => ({ response })).catch((error) => { console.log("JSON cannot be parsed") });
}
error = JSON.stringify(response);
return ({ error });
}).catch((error) => {
console.log("Promise rejected");
});
}
onButtonPress = async () => {
const userid = this.state.userid;
const realDate = this.state.realDate;
const { response, error } = await this.fetchData('http://myip/api/hours/daydata/${realDate}/${userid}', 'GET');
if (response) {
console.log(response);
this.setState({
remark: response[0].remark
});
} else {
console.error("Error: " + error);
}
}

Related

Is there a better way to store data from an API in state?

I'm developing the interface of a music web app , so i fetched data from an API and stored in state, all executed by one function , to be displayed on the interphase .The code is below :
/* function fetching the data*/
function getUsChart() {
const options = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '036795ec2amsh8c2b98ef8a502acp146724jsn6f3538b26522',
'X-RapidAPI-Host': 'shazam-core.p.rapidapi.com'
}
};
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => setUsHopChart(response))
.catch(err => console.error(err));
/*I stored it in state here*/
setChartImg(usHopChart[1]?.images?.coverart)
}
/*I displayed it here*/
<img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>
The issue:
After the function is executed , the data is fetched but not stored it in the state immediately until it's executed the second time. Hence causing this :
What can i do about this please?
i think you need to move the setChartImg inside
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => {
setUsHopChart(response)
setChartImg(response[1]?.images?.coverart)
})
.catch(err => console.error(err));
/*I stored it in state here*/
I think the problem is jsx is rendered before the fetch process is done. So, it is the best approach to create a boolean loading state and initialize it with true, when it's value is true create a spinner or smth and make it false when promise returns the value.
For quick solution maybe you can do something like that:
{chartImg && <img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>}
So what it does is when chartImg is defined (when you give it a value after promise resolves) it will render the jsx element, which was your problem.
I think you want to fetch data faster and store it in the state. There is a way to that. I will give you an example
const commentsPromise = fetch('/get-comments');
const Comments = () => {
useEffect(() => {
const dataFetch = async () => {
// just await the variable here
const data = await (await commentsPromise).json();
setState(data);
};
dataFetch();
}, [url]);
}
In this example our fetch call basically “escapes” all React lifecycle and will be fired as soon as javascript is loaded on the page, before any of useEffect anywere are called.
Even before the very first request in the roop App component will be called. It will be fired, javascript will move on to other things to process, and the data will just sit there quietly until someone actually resolves it.

Vue.js with vuex and axios - can get data only on second load

I created a Vue.js app with a central store with vuex and some basic API calls with axios to fetch data into the store.
I create the following store action:
loadConstituencyByAreaCodeAndParliament({commit}, {parliament_id, area_code}) {
axios.get('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then((response) => {
commit('SET_CONSTITUENCY', response.data);
})
.catch(function(error){
commit('SET_CONSTITUENCY', null);
}
)
}
In a single component file I defined a form where the user enters the area code. This form then calls this action to get the constituency fitting the area code:
export default {
name: 'AreaCodeForm',
components: {
PostalCodeInput
},
props: ['parliament_id'],
data: () => ({
postalCode: ''
}),
methods: {
search_area_code(submitEvent) {
let area_code = submitEvent.target.elements.area_code.value;
let payload = {
parliament_id: this.parliament_id,
area_code
}
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload).
then(() => {
let constituency = this.$store.getters.getConstituency();
// do some things with the data received from the API
// but everything depending on constituency does not work the first time.
// Data received from the API is here available only from the second time on
// wehen this code run.
})
}
}
}
As I found out the $store.dispatch method returns a promise but still the constituency variable receives not the data fetched with the loadConstituencyByAreaCodeAndParliament action but remains empty. I thought when I use the promise.then method the data should be already stored in the store but it is not. When I enter the area code a second time everything works well.
As mentioned by blex in a comment returning the axios call is the answer:
loadConstituencyByAreaCodeAndParliament({commit}, {parliament_id, area_code}) {
return axios.get('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then((response) => {
commit('SET_CONSTITUENCY', response.data);
})
.catch(function(error){
commit('SET_CONSTITUENCY', null);
}
)
}
Always remember the return statement when dealing with asyncronous tasks.
You have two options to refactorize your code, keeping promise or async/await.
Option 1: async/await
async loadConstituencyByAreaCodeAndParliament({ commit }, { parliament_id, area_code }) {
try {
const { data } = await axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
commit('SET_CONSTITUENCY', data)
return data
} catch (error) {
commit('SET_CONSTITUENCY', null)
return error
}
}
Notes:
return statement in both blocks of try/catch.
.get in axios is optional, since default is get method.
You can use object Destructuring assignment with { data } by default with axios. If I'm not wrong the default good http responses retrieve data.
Even a more sophisticated way could be const { data: constituencyResponse } = await... then you work with constituencyResponse and you probably save 2 or 3 lines of code each time.
Option 2: Promise
First Path: Make everything in the store.
// actions
loadConstituencyByAreaCodeAndParliament({ commit, dispatch }, { parliament_id, area_code }) {
axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then(({data}) => {
commit('SET_CONSTITUENCY', data)
dispatch('actionTwo', constituency)
})
.catch((error) => {
console.log("error", error)
commit('SET_CONSTITUENCY', null)
})
}
actionTwo({commit}, constituency) {
console.log("actionTwo", constituency)
// do something
commit('COMMIT', 'Final value')
}
// Component
// You handle it with a computed property either referencing a getter or the store state.
{
computed: {
getConstituency(){
return this.$store.state.constituency
},
getSomeOtherConstituency(){
return this.$store.state.constituency.something / 3
}
},
// Optionally if you want to listen and react to changes use a `watcher`.
watch: {
// Gets excecuted each time getConstituency updates.
// ! Must have the same name.
getConstituency(update) {
// Do something, `update` is the new value.
}
}
}
Second Path: Handle data inside the component, then update the store.
Vue component.
methods: {
search_area_code(submitEvent) {
const parliament_id = this.parliament_id
const area_code = submitEvent.target.elements.area_code.value
axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then(({data: constituency}) => {
this.$store.commit('SET_CONSTITUENCY', constituency)
// Do whatever you want with constituency now inside the component.
})
.catch((error) => {
console.log("error", error)
this.$store.commit('SET_CONSTITUENCY', null)
})
}
},
Notes:
$store.dispatch method returns a promise but still the constituency variable receives not the data fetched with the loadConstituencyByAreaCodeAndParliament action but remains empty.
When I enter the area code a second time everything works well.
I think the problem here is that you either handled bad the asyncronous code or trying to implement a custom pattern to work around.
As I said earlier put store getters in computed properties,
Look at this example in the Vuex-docs.
Code insights:
// Your action doesn't return anything, you must `return axios.get` inside it.
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload).then(() => {
let constituency = this.$store.getters.getConstituency()
})
// without the `return` statement the code above can be translated to
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload)
let constituency = this.$store.getters.getConstituency()
// If you opt for async a valid way would be
async doSomething(){
await this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload)
let constituency = this.$store.getters.getConstituency()
}
// IF it still doesnt update anything try `$nextTick` https://vuejs.org/v2/api/
this.$nextTick(() => {
this.data = this.$store.getters.getConstituency()
})
I hope some of this has been helpful.

How to pass action to another action if no params are present ReactJs

I have 2 actions in my code. One is getting data based on year month and user id.
The other one is updating data based on some other params.
Upon success of second method, I want to run first action so that my component will re-render with the updated contents.
export const GetConsultantProjects = (userId, month, year) => {
return (dispatch) => {
dispatch({type: START_LOADING})
globalAxios().get(`/api/v1/time_registrations`, {
params: {
user_id: userId,
month: month,
year: year
}
}).then(resultSet => {
if (resultSet) {
dispatch({type: STOP_LOADING})
dispatch({type: GET_CONSULTANT_PROJECTS, payload: resultSet.data})
}
}).catch(err => {
if (err.response) {
console.log(err)
}
})
}
}
export const UpdateProjectTime = (hour, date, projectId, userId, token, activity, timeId) => {
let bodyFormData = new FormData();
if(token && token.length !== 0) {bodyFormData.append('project[project_times_attributes][0][token]', token);}
if(activity && activity.length !== 0) {bodyFormData.append('project[project_times_attributes][0][activity]', activity);}
if(timeId && timeId.length !== 0) {bodyFormData.append('project[project_times_attributes][0][id]', timeId);}
bodyFormData.append('project[project_times_attributes][0][user_id]', userId);
bodyFormData.append('project[project_times_attributes][0][date]', date);
bodyFormData.append('project[project_times_attributes][0][project_id]', projectId);
bodyFormData.append('project[project_times_attributes][0][hours]', hour);
return (dispatch) => {
dispatch({type: START_LOADING})
globalAxios().put(`/api/v1/time_registrations/${projectId}`, bodyFormData)
.then(resultSet => {
if (resultSet) {
dispatch(GetConsultantProjects()) // I want to run it again but I have no params here. How to pass them and run it again??
dispatch({type: UPDATE_PROJECT_TIME, payload: resultSet.data})
dispatch({type: STOP_LOADING})
}
}).catch(err => {
if (err.response) {
console.log(err)
}
})
}
}
dispatch(GetConsultantProjects())
I want to run it again but I have no params here. Like month, year and user_id.
How to pass them and run it again??
When it comes to calling a function and calling another function by looking at the first ones success status, you definitely need a way to handle the success state of the first function. The below sandbox is a sample solution for achieving the thing you have mentioned here. What I am doing is getting a success response from the first function as a prop and fire the second function in componentWillUpdate.
That is the most popular version of calling a function by looking at the other functions behavior in react developers. I hope that would give some help to tackle your problem. if there is something to add or need to change mention it in the comment section.
Sandbox Link : https://codesandbox.io/s/call-function-by-looking-at-another-functions-status-rn7hg
Open the solution in a new window and debug. You will see how the console log are coming and what is the flow of calling functions.

How can I save in state the response of an Api Call?

I'm trying to save in the state of my component the data an Api call retrieves, but the data have no time to come cause of the async function so when I check the state its value is an empty array. Here is the code.
async getValuesData() {
let id = "dataid";
let getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
const res = await response.json()
this.setState = ({
values: res
})
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
I'm calling the function in the contructor.
First, you've to call the function inside ComponentDidMount lifecycle if you want the component to appear as soon as the data is mounted
Second,I'd do the following:
I declare, either in the same file or in a different one, for example, x.business.js the function that calls the backend and returns the result:
const getValuesData = async () => {
const id = "dataid";
const getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
return await response.json();
}
Then in the component, if I want it to be called as soon as it is assembled, this is when I make the assignment to its state (and if you want to check that it has been set, you use the callback that setState has after the assignment):
class SampleComponent extends React.Component {
state = {
values: {}
}
componentDidMount() {
getValuesData().then(response =>
this.setState(prevState => ({
...prevState,
values: response
}), () => {
console.log(this.state.values);
}))
}
...
}
As the community says, it's all in the documentation:
componentDidMount: if you need to load data from a remote endpoint and update your state
setState(): to update state of the component
Here's an example of how it would work
You're calling setState incorrectly. It should be:
this.setState({ values: res });
The console.log() calls, even if you adjust the above, won't show accurately what you expect. If that's what you want try this too:
this.setState({ values, res },
() => {
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
);
I.e., make the console.log()'s the second argument to setState which will then accurately show the value of state.
You should do this :
this.setState({values: res})
this.setState should be a function : https://reactjs.org/docs/react-component.html#setstate
You have to use setState instead of this.state = {}
this.setState({values: res})
Use like this
this.setState({values: res})
use this.setState() to schedule updates to the component local state
Do Not Modify State Directly
// Wrong
this.state.values = res
Instead, use setState()
// Correct
this.setState({values: res});

setState not working inside AsyncStorage in react native?

setState not working inside AsyncStorage in React Native.
constructor(props) {
super(props);
this.state = {userId: ''};
}
componentDidMount() {
AsyncStorage.getItem('USER_ID', (err, result) => {
if (!err && result != null) {
this.setState({
userId: result
});
}
else {
this.setState({
userId: null
});
}
});
alert(this.state.userId);
let userId = this.state.userId;
fetch('http://localhost/JsonApi/myprofile.php', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: userId,
}),
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({userDetails: responseJson});
})
.catch((error) => {
console.error(error);
});
}
Setting the userId value using setState and alert returns no value at all. Tried other solutions from Stackoverflow but not as per my expectation.
Note: Code updated. After getting userId from AsyncStorage, it will be passed to fetch. Here, userId value is missing.
2 ways to do this. One is Simple but other is correct way according to react recommendation
One is here- pass value to state directly.
.then((responseJson) => {
// this.setState({userDetails: responseJson});
this.state.userDetails=responseJson;
this.setState({}); //for update render
})
Second Way is here
in the render function Check state Value like this .if UserDetails state is null it will be not give you error whenever userDetails state get data render execute again and provide perfect result.
render() {
return (
<div>
{this.state.userDetails ?
this.state.userDetails.map((data, index) =>
<tr key={index}>
<td>{data.userName}</td>
<td>{data.userEmail}</td>
</tr>
)
: null
}
</div>)}
Let me know gain. if facing issue
Try to alert after updating state. You will get callback once state is updated.
this.setState({
userId: result
},function(){
console.log("userId in async = ", this.state.userId);
alert(this.state.userId);
});
I don't know why you wrote so much code.
First way
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
});
You don't need to check error & result both because if that is null, you are setting userId null in state. so you can directly set value to state userId.
Also set a log to see what is output of your async storage userId.
Please also verify that you are setting value in "USER_ID" somewhere.
Second way
There can different ways also like using async method.
const getUserId = async () => {
try {
const userId = await AsyncStorage.getItem('USER_ID') || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return userId;
}
and you can use
this.setState ({
userId : getUserId()
});
I don't like this way because I need to create another method with async & await keywords.
I use the first way so.
Update
Do your work related to userId inside getIten();, because you alert userId immediately after calling AsyncStorage. And AsyncStorage returns value after you call alert.
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
alert(this.state.userId); // move this line here
});
// removed from here

Categories