React Native setState doesn't effect immediately data - react-native

I am developing a project with react native.After using axios fetch data , then I changed my billingList state using returning data,but after that when I want logged this state,my array is blank.Data is returning but state doesn't change immediately.
axios.get(url, config)
.then(function (response) {
if (response.data.status === true) {
console.log(response.data.data);
setBillingList(response.data.data)
console.log(billingList);
}
})
.catch(function (error) {
console.log(error);
})
}
So using this data in responsive table,table is blank .
import { TableView } from "react-native-responsive-table"
return (
<View>
<TableView
headers={[
{
name: "S.no.",
reference_key: "no",
},
{
name: "Name",
reference_key: "name",
},
{
name: "Age",
reference_key: "age",
},
]}
rows={billingList}
/>
</View>
)

The reason is that state update does not happen immediately. This is because when we call the setState function, the entire component gets re-rendered – so React needs to check what all needs to be changed using the Virtual DOM algorithm and then perform various checks for an efficient update of the UI.
This is the reason you may not get the updated value instantly.

Try putting your axios request in useEffect:
useEffect(() => {
axios.get(url, config)
.then(function (response) {
if (response.data.status === true) {
console.log(response.data.data);
setBillingList(response.data.data)
console.log(billingList);
}
})
.catch(function (error) {
console.log(error);
})
}
}, [])
I have done this multiple times and it should work!

Related

Promise not waiting for store.dispatch

I've just started learning Vue, and am working on porting my django project to Vue. I started out simple. My objective is to create a component, which on loading, would use Axios to fetch a list of patients from my server.
So in my component, I wrote:
export default {
data() {
return {
patients: [],
};
},
created() {
console.log(`Created Component:Patient Waiting`);
this.$store
.dispatch("getPatientList", this.today)
.then(() => {
console.log(`Finished dispatch of getPatientList from component.`);
this.patients = this.$store.getters.patientNotSeenList;
console.log(`Now, this.patients is:`);
console.log(this.patients);
})
.catch((error) => {
console.log("Got error 2");
console.log(error);
});
},
};
Template:
<p v-for="patient in patients" :key="patient.checkinno">
{{ patient.checkinno }}
</p>
In my vuex store, I have:
export default createStore({
state: {
},
getters: {
patientNotSeenList: (state) => {
console.log(`In store, in getter:patientNotSeenList:`);
return state.patientNotSeenList;
},
},
mutations: {
STORE_PATIENT_LIST(state, data) {
state.patientSeenList = data.seen
state.patientNotSeenList = data.notseen
},
},
actions: {
getPatientList({ commit }, date) {
console.log(`[In Store::getPatientList, getting patient list...]`);
axios
.get(constants.API_GETPATIENT_LIST, {
params: {
....
},
})
.then(({ data }) => {
console.log(`Got data is`);
console.log(data);
let patientSeen = data.results.filter(
(checkin) => checkin.consulted == 1
);
let patientNotSeen = data.results.filter(
(checkin) => checkin.consulted == 0
);
console.log(`patientSeen is`);
console.log(patientSeen);
console.log(`patientNotSeen is`);
console.log(patientNotSeen);
console.log(`[Finished action for Store::getPatientList]`);
commit("STORE_PATIENT_LIST", {
seen: patientSeen,
notseen: patientNotSeen,
});
})
.catch((error) => {
console.log(
"In Store::getPatientList,Could not get data from API. Maybe not logged in, or dont have token?"
console.log(error);
)})
},
}
The problem I am having is that even though I am using a promise, the data is being rendered before the action is completed and mutation commited from store.
My console log looks like this:
Created Component: Patient Waiting
index.js?4360:141 [In Store::getPatientList, getting patient list...]
PatientWaiting.vue?110a:144 Finished dispatch of getPatientList from component.
index.js?4360:33 In store, in getter:patientNotSeenList:
PatientWaiting.vue?110a:146 Now, this.patients is:
PatientWaiting.vue?110a:147 undefined
TopBar.vue?92f9:90 Route location from TopBar: PatientWaiting
index.js?4360:150 Got data is
index.js?4360:151 {count: 1, next: null, previous: null, results: Array(1)}
index.js?4360:152 Results is
index.js?4360:153 [{…}]
index.js?4360:160 patientSeen is
index.js?4360:161 []
index.js?4360:162 patientNotSeen is
index.js?4360:163 [{…}]
index.js?4360:164 [Finished action for Store::getPatientList]
So I end up with an empty list. Why is this going wrong?
You are not returning the Promise axios.get(..).then(..) creates in getPatientList({ commit }, date) and thus the then() in your Component is immediately called. Change getPatientList to:
getPatientList({ commit }, date) {
console.log(`[In Store::getPatientList, getting patient list...]`);
return axios.get(constants.API_GETPATIENT_LIST, {
params: {
....
},
}).then(({ data }) => {
console.log(`Got data is`);
console.log(data);
let patientSeen = data.results.filter(
(checkin) => checkin.consulted == 1
);
let patientNotSeen = data.results.filter(
(checkin) => checkin.consulted == 0
);
console.log(`patientSeen is`);
console.log(patientSeen);
console.log(`patientNotSeen is`);
console.log(patientNotSeen);
console.log(`[Finished action for Store::getPatientList]`);
commit("STORE_PATIENT_LIST", {
seen: patientSeen,
notseen: patientNotSeen,
});
})
.catch((error) => {
console.log(
"In Store::getPatientList,Could not get data from API. Maybe not logged in, or dont have token?"
console.log(error);
)})
},

Reset useLazyQuery after called once

I'm using useLazyQuery to trigger a query on a button click. After the query is called once, the results (data, error, etc) are passed to the component render on each render. This is problematic for example when the user enters new text input to change what caused the error: the error message keeps reapearing. So I would like to "clear" the query (eg. when user types new data into TextInput) so the query results return to there inital state (everything undefined) and the error message goes away.
I can't find any clear way to do this in the Apollo docs, so how could I do that?
(I though of putting the query in the parent component so it does not update on each rerender, but I'd rather not do that)
This is how I have my component currently setup:
import { useLazyQuery } from 'react-apollo'
// ...
const [inputValue, setInputValue] = useState('')
const [getUserIdFromToken, { called, loading, data, error }] = useLazyQuery(deliveryTokenQuery, {
variables: {
id: inputValue.toUpperCase(),
},
})
useEffect(() => {
if (data && data.deliveryToken) {
onSuccess({
userId: data.deliveryToken.vytal_user_id,
token: inputValue,
})
}
}, [data, inputValue, onSuccess])
// this is called on button tap
const submitToken = async () => {
Keyboard.dismiss()
getUserIdFromToken()
}
// later in the render...
<TextInput
onChangeText={(val) => {
setInputValue(val)
if (called) {
// clean/reset query here? <----------------------
}
})
/>
Thanks #xadm for pointing out the solution: I had to give onCompleted and onError callbacks in useLazyQuery options, and pass the variables to the call function, not in useLazyQuery options. In the end the working code looks like this:
const [inputValue, setInputValue] = useState('')
const [codeError, setCodeError] = useState<string | undefined>()
const [getUserIdFromToken, { loading }] = useLazyQuery(deliveryTokenQuery, {
onCompleted: ({ deliveryToken }) => {
onSuccess({
userId: deliveryToken.vytal_user_id,
token: inputValue,
})
},
onError: (e) => {
if (e.graphQLErrors && e.graphQLErrors[0] === 'DELIVERY_TOKEN_NOT_FOUND') {
return setCodeError('DELIVERY_TOKEN_NOT_FOUND')
}
return setCodeError('UNKNOWN')
},
})
const submitToken = () => {
Keyboard.dismiss()
getUserIdFromToken({
variables: {
id: inputValue
},
})
}

Vuex update state by using store actions

I have two functions in my store, one that gets data by calling API and one that toggles change on cell "approved". Everything working fine, except that when I toggle this change it happens in database and I get the response that it is done but It doesn't update on UI.
I am confused, what should I do after toggling change to reflect change on UI, should I call my API from .then or should I call action method responsible for getting data from server.
export default {
state: {
drivers: {
allDrivers:[],
driversError:null
},
isLoading: false,
token: localStorage.getItem('token'),
driverApproved: null,
driverNotApproved: null
},
getters: {
driversAreLoading (state) {
return state.isLoading;
},
driverError (state) {
return state.drivers.driversError;
},
getAllDrivers(state){
return state.drivers.allDrivers
}
},
mutations: {
getAllDrivers (state) {
state.isLoading=true;
state.drivers.driversError=null;
},
allDriversAvailable(state,payload){
state.isLoading=false;
state.drivers.allDrivers=payload;
},
allDriversNotAvailable(state,payload){
state.isLoading=false;
state.drivers.driversError=payload;
},
toggleDriverApproval(state){
state.isLoading = true;
},
driverApprovalCompleted(state){
state.isLoading = false;
state.driverApproved = true;
},
driverApprovalError(state){
state.isLoading = false;
state.driverError = true;
}
},
actions: {
allDrivers (context) {
context.commit("getAllDrivers")
return new Promise((res,rej)=>{
http.get('/api/admin/getAllDrivers').then(
response=>{
if (response.data.success){
let data=response.data.data;
data=data.map(function (driver) {
return {
/* response */
};
});
context.commit("allDriversAvailable",data);
res();
}else {
context.commit("allDriversNotAvailable",response.data)
rej()
}
})
.catch(error=>{
context.commit("allDriversNotAvailable",error.data)
rej()
});
});
},
toggleDriverApproval (context, payload){
return new Promise((res, rej)=>{
http.post("/api/admin/toggleDriverApproval",{
driver_id: payload
})
.then( response => {
context.commit('driverApprovalCompleted');
res();
}).catch( error =>{
context.commit('driverApprovalError');
rej()
})
})
}
}
}
and here is the code on the view, I wrote the necessary code for better clarification of the problem
export default {
name: 'Drivers',
data: () => ({
data: [],
allDrivers: [],
driversErrors: []
}),
created() {
this.$store
.dispatch('allDrivers')
.then(() => {
this.data = this.$store.getters.getAllDrivers
})
.catch(() => {
this.errors = this.$store.getters.driverError
})
},
computed: {
isLoading() {
return this.$store.getters.driversAreLoading
}
},
methods: {
verify: function(row) {
console.log(row)
this.$store.dispatch('toggleDriverApproval', row.id).then(() => {
this.data = this.$store.getters.getAllDrivers
console.log('done dis')
})
},
},
}
if I understand your issue, you want the UI displaying your data to change to the updated data after making a post request.
If you are using Vuex you will want to commit a mutation, and use a getter display the data.
I am not sure how your post request is being handled on the server but if successful typically you would send a response back to your front end with the updated data, and commit a mutation with the updated data.
Example:
Make a Post request
toggleDriverApproval (context, payload){
return new Promise((res, rej)=>{
http.post("/api/admin/toggleDriverApproval",{
driver_id: payload
})
.then( response => {
context.commit('driverApprovalCompleted', response.data);
res();
}).catch( error =>{
context.commit('driverApprovalError', error.response.data);
rej()
})
})
}
If succesful commit the mutation
.then( response => {
context.commit('driverApprovalCompleted', response.data);
res();
})
response.data being your data you want to mutate the state with.
Mutation Example:
customMutation(state, data) {
state.driverApproval = data
}
Getter Example:
driver(state) {
return state.driverApproval
}
displaying the getter in a template
<template>
<div v-if="driver">{{driver}}</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: Example,
computed: {
driver() {
return this.$store.getters.driver
},
// or use mapGetters
...mapGetters(['driver'])
}
}
</script>
more examples can be found at Vuex Docs

Vue component doesn't update on route URL parameter change

So I have a component which executes code once it's mounted like this:
mounted(){
axios.get('/markers/' + this.username)
.then(response => {
this.markers = response.data.markers
}).catch((error) => console.log(error));
}
And I get the username like this:
username: this.$route.params.username
however, if I change the URL parameter, the username doesn't update so my AXIOS call doesn't update my markers. Why is this happening?
The reason is simple, even thought the URL is changing the component is not, VueJS is basically reusing the component and therefore not calling the mounted() method again.
Usually you can just setup a watcher and refactor a bit your code
methods: {
fetchData(userName) {
axios.get('/markers/' + this.username)
.then(response => {
this.markers = response.data.markers
}).catch((error) => console.log(error));
}
},
watch: {
'$route.params': {
handler(newValue) {
const { userName } = newValue
this.fetchData(userName)
},
immediate: true,
}
}
EDIT: Added the immediate true option and removed the mounted() hook

React Native setState(…) warning with both componentWillMount and componentDidMount

I'm starting with react-native and in my project I got to a point where everything works but there's this warning:
Warning: setState(...): Can only update a mounted or mounting component.
So, I've looked several QA, tried a few solutions(changing the setState() call from componentWillMount and componentDidMount) but... the warning is always there.
Here is part of the code:
REQUEST_URL = 'http://url/users.php';
(...)
module.exports = React.createClass({
getInitialState: function() {
return {
uid: null,
bid: null,
username: null,
}
},
componentDidMount: function() {
this.fetchData();
},
fetchData: function() {
fetch(REQUEST_URL)
.then( (response) => response.json() )
.then( (json) => {
console.log('setState called');
this.setState({
uid: json.users[0].user_id,
bid: json.users[0].building_id,
username: json.users[0].username
});
})
.done();
},
render: function() {
if (!this.state.uid) { //user is not defined
console.log('not rendered');
return <Text>chargement...</Text>
}
// else
console.log('rendered');
var userId = this.state.uid;
var buildingId = this.state.bid;
var username = this.state.username;
return (
<View style={styles.content}>
<Text style={styles.label}>User Id</Text>
<Text>{userId}</Text>
<Text style={styles.label}>Building Id</Text>
<Text>{buildingId}</Text>
<Text style={styles.label}>Username</Text>
<Text>{username}</Text>
</View>
)
},
});
The users.php returns a json content-type.
Any clues?
Thanx.
The problem may be that react re-mounts certain components multiple times in one render (think that has something to do with the representation of initial values, could not find the question here), therefore your state would be set to a component that is not mounted.
If you set your state in a decoupled timeout that can be cleared when the component unmounts, you avoid setting state on a unmounted component.
componentDidMount() {
this.mounted = true;
// this.fetchTimeout = setTimeout(()=>{
this.fetchData();
// });
},
componentWillUnmount() {
// clearTimeouts(this.fetchTimeout);
this.mounted = false;
},
fetchData() {
fetch(REQUEST_URL)
.then( (response) => response.json() )
.then( (json) => {
console.log('setState called');
if (this.mounted === true){
this.setState({
uid: json.users[0].user_id,
bid: json.users[0].building_id,
username: json.users[0].username
});
}
})
.done();
},
I still don't know if we are supposed to use TimerMixins but this way works without those.
(TimerMixins take care of clearing any timeout or interval set in the component)
EDIT: update sample to only call setState of the component is still mounted.
I do not know if there is a better way, but as far as I know until now you can not cancel a fetch request.