passing 2 functions to useEffect and getting an error "Can't perform a React state update on an unmounted component." - javascript

I need to pass 2 functions to my useEffect -
one that renders the whole page and sends some general data to the server,
another one is voting function and sends vote results (called "answer") to the server.
I tried to put both functions inside one useEffect, as well as using useEffect twice. Will show code for both of them.
2 useEffects was working for a little bit, then I started getting this error: "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function".
So, my question is:
Can I call 2 functions under one useEffect or can I have 2 useEffects?
How to get rid of my unmount error? I tried setting isMounted to true/false but still no luck.
Code for both functions in one useEffect:
useEffect(() => {
let isMounted = true;
function viewingPoll() {
fetch(`/api/polls/${pollIview}`)
.then((resp) => resp.json())
.then((result) => {
if (isMounted) {
console.log("my result on pollvoter is", result);
if (result.noSuchPoll) {
setPoll(false);
} else {
setPoll(result.pollInfo);
console.log("result right here", result); //all the happy points
}
}
})
.catch((err) => {
console.log("error in pollvoter.js ", err);
this.setState({
error: true,
});
});
}
viewingPoll();
function voting() {
let isMounted = true;
fetch("/api/vote", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
poll_id: poll.id,
answer: answer,
}).then(() => {
if (isMounted) {
// location.redirect(`/polls/${result.pollId}`);
location.reload();
}
}),
});
}
voting();
return () => {
isMounted = false;
};
}, [answer]);
Example 2: having 2 separate useEffects
useEffect(() => {
let isMounted = true;
function viewingPoll() {
fetch(`/api/polls/${pollIview}`)
.then((resp) => resp.json())
.then((result) => {
if (isMounted) {
console.log("my result on pollvoter is", result);
if (result.noSuchPoll) {
setPoll(false);
} else {
setPoll(result.pollInfo);
console.log("result right here", result); //all the happy points
}
}
})
.catch((err) => {
console.log("error in pollvoter.js ", err);
this.setState({
error: true,
});
});
}
viewingPoll();
}, [answer]);
useEffect(() => {
let isMounted = true;
fetch("/api/vote", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
poll_id: poll.id,
answer: answer,
}).then(() => {
if (isMounted) {
// location.redirect(`/polls/${result.pollId}`);
location.reload();
}
}),
});
return () => {
isMounted = false;
};
}, [answer]);

Related

React-native AsyncStorage Issue

In the code snippet you see, I am trying to reach the data that I have determined through asyncStorage in the getToken and `` functions, but when I open the page with these codes from the emulator, the data is empty for the first time, and then when I do ctrl+s from the editor, the data is full. What is the reason for this problem?
App.js Page
getToken: async () => {
const token = AsyncStorage.getItem('userToken');
return token;
},
getMail: async () => {
const mail = AsyncStorage.getItem('userMail');
return mail;
},
OrderListScreen Page
getToken().then((res) => {
if(res){
setToken(res);
console.log(token)
}else {
setToken('');
}
});
getMail().then((res) => {
if(res){
setMail(res);
console.log(mail)
}else {
setMail('');
}
});
Apply await before using AsyncStorage.getItem:
getToken: async () => {
const token = await AsyncStorage.getItem('userToken');
return token;
},
getMail: async () => {
const mail = await AsyncStorage.getItem('userMail');
return mail;
},
In the log you'll not get the updated state in next line of state setter.
getToken().then((res) => {
if(res){
setToken(res);
console.log(token); //You'll never get this value here because state updates are asynchronous in React
console.log("res : ", res);
}else {
setToken('');
}
});
getMail().then((res) => {
if(res){
setMail(res);
console.log(mail)//You'll never get this value here because state updates are asynchronous in React
console.log("Email Res : ", res);
}else {
setMail('');
}
});

How to wait for action to complete before accessing the Vue Store state?

I have Vuejs/Nuxtjs application within which I need to access a Vuex store state after it has been modified by Vuex action. Currently when I try to run the action and assignment then I get the old state and not the one which was updated after action.
How to make the code wait for action completion then run the next statement? Following is the code I have currently:
Vuejs Component:
<template>
<div>
<input v-model="formData.value" type="text">
<button #click="performAction">
Click Me
</button>
</div>
</template>
<script>
export default {
data () {
return {
formData: {
value: '',
returnValue: ''
}
}
},
methods: {
performAction () {
// Set the value within the Vuex Store
this.$store.commit('modules/DataStore/populateData', this.formData.value)
// Perform the Action
this.$store.dispatch('modules/DataStore/getData').then(() => {
console.log("AFTER COMPLETE ACTION")
})
// Assign the update value to the variable
this.formData.returnValue = this.$store.state.modules.DataStore.data
}
}
}
</script>
<style>
</style>
Vuex Store:
export const state = () => ({
data:''
})
export const mutations = {
populateData (state, data) {
state.data = data
}
}
export const actions = {
getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
this.$axios
.post('/getUrlData', state.data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data)
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
}
Following are the thing I tried and nothing is working at the moment:
I tried the .then() function.
I tried Async and await but both are not working
Any suggestions will be really appreciated. Thanks in advance.
You can create getter in vuex :
export const getters = {
getData: (state) => state.data,
};
export const actions = {
async setData ({ commit }, data) {
const headers = { 'Content-Type': 'application/json' }
await this.$axios
.post('/getUrlData', data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data)
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
}
then in component you can map getters and actions, and call them :
import { mapGetters, mapActions } from 'vuex'
computed: {
...mapGetters(['getData']),
},
methods: {
...mapActions(['performAction']),
async performAction() {
await this.setData(this.formData.value)
this.formData.returnValue = this.getData
}
}
You need to return your promise in your if you want to chain it in the calling method. eg:
getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
return this.$axios // now this promise will be returned and you can chain your methods together
.post('/getUrlData', state.data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data);
return response.data; //this will allow you do send the data through to the next Then() call if you want to
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
This situation is a lot easier to manage with async-await IMO. It becomes:
export const actions = {
async getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
const response = await this.$axios.post('/getUrlData', state.data, { headers });
console.log("WITHIN RESPONSE")
commit('populateData',response.data);
}
}
and
methods: {
async performAction () {
// Set the value within the Vuex Store
this.$store.commit('modules/DataStore/populateData', this.formData.value)
// Perform the Action
await this.$store.dispatch('modules/DataStore/getData');
console.log("AFTER COMPLETE ACTION");
// Assign the update value to the variable
this.formData.returnValue = this.$store.state.modules.DataStore.data
}
}

How to wait promise solved afterthen render in reactjs

I got this error when using promise:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I'm pretty sure this error is got from my promise async request, when i not using promise it working fine.
But when im using promise to handle async request, i got this error, i need promise to handle it, so how can i fix this?
This is my Async Request.
export const LoginAPI = (username, password) => (dispatch) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({ username, password });
const promise = new Promise((resolve, reject) => {
axios.post('http://localhost:8000/api/accounts/login/', body, config)
.then((response) => {
dispatch({ type: LOGIN_SUCCESS, payload: response.data });
SwalAlert('Successfully Login...', 'Welcome To 7Blankpages...', 'success');
resolve(true);
})
.catch((err) => {
dispatch({ type: LOGIN_FAILED });
if (err.response.data.non_field_errors) SwalAlert('Oops...', `${err.response.data.non_field_errors}`, 'error');
reject(false);
});
});
return promise;
};
This is my Login Handling
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
const response = await LoginUser(username, password).catch(() => setIsLoading(false));
if (response) {
setIsLoading(false);
setUsername('');
setPassword('');
}
};
if (auth.isAuthenticated && auth.user.is_active) {
return <Redirect to="/" />;
}
This happened because your handlesubmit function is asynchronous that makes if block to get executed redirecting to url '/' changing the component. but the function setisloading,setusername etc in your handlesubmit will still be excecuting which led to memory leak. To prevent this add useEffect hook with cleanup function.
useEffect(()=>{
//cleanup function
return ()=>{
setisloading(false);
setusername('');
setpassword('');
}
})

Warning: Can't call setState (or forceUpdate) on an unmounted component in React Native

Full error image:
error image
I am making a fetch request and when I want to set the state to save some errors that happens. How do I fix this?
Code:
onClickLogIn = (username, password) => {
const request = fetch('[SOMEAPILINK]', {
method: 'POST',
headers: {
Accept: 'text/javascript',
'Content-Type': 'text/javascript',
},
body: JSON.stringify({
username: username,
password: password,
login: 1
})
}).then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
})
}).catch(error => {
console.log("error")
})
// console.log(errorCheck);
console.log(request);
console.log("----ERROR CHECK ----")
console.log(this.state.errorCheck)
this.props.navigation.navigate("Second")
}
So when I want to set errorCheck that error comes in...
Thanks!
then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
})
this.props.navigation.navigate("Second")
})
=> Add this code this.props.navigation.navigate("Second") of navigation inside the then() method so it will call navigation after updating the state then your error will gone.
=> and try to update the state using setState function not an object so try
this.setState(function (prevState, props) {
return { errorCheck: responseJson.error}
})
it will reduce the time , taken by the object to update the state.
=> So your code will look like
then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState(function (prevState, props) {
return { errorCheck: responseJson.error}
})
this.props.navigation.navigate("Second")
})
setState is asynchronous. So if you unmount the stateful component (by calling navigate) before updating the state then you'll get the warning.
You should use the callback that setState provides instead
.then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
}, () => {
this.props.navigation.navigate("Second")
})
})

How do I mock two fetches or one Promise and one fetch?

I am trying to test the loadAllProjects function.
The test fails at .then() with the error: TypeError: Cannot read property 'then' of undefined
I have also tried mocking the reponse of getHeadersWithToken() but could not get it to work.
Snookered on this one and would appreciate any help.
test:
it('should create SET_ALL_PROJECTS action when fetching projects', () => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
store.dispatch(actions.loadAllProjects.apply())
.then(() => { // FAILS HERE
expect(store.getActions()).toEqual(expectedActions)
})
});
code:
export const getHeadersWithToken = () => {
return fetch("/.auth/me", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
const header = 'Bearer ' + json[0].access_token
const applicationJsonHeaders = getJsonHeaders(header)
return applicationJsonHeaders
})
.catch( error=> {
console.error(error)
})
}
export const loadAllProjects = () => {
return (dispatch) => {
getHeadersWithToken()
.then(applicationJsonHeaders => {
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
dispatch(setAllProjects(json))})
.catch(error => {
console.error(error)
dispatch(failedToLoadProjects(error))
});
})
}
}
store used in test:
const store = mockStore(Map(
{
allProjects: Map({
}),
currentProject: Map({
authenticationData: Map({
})
})
})
);
What Redux middleware are you using for the async stuff? Make sure you set up the middleware when creating the store for testing.
Since i dont see that anywhere in your code above im gonna assume we are not using that middleware here.
Since loadAllProjects is a higher order function i would do this:
it('should create SET_ALL_PROJECTS action when fetching projects', (done) => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
// Higher order function that returns a new function.
const loadAllProjectsAsync = actions.loadAllProjects();
// The function returned expects a dispatch from Redux as an argument.
// It will do async work and when its done, it will call the provided dispatch.
loadAllProjectsAsync(store.dispatch).then(() => {
expect(store.getActions()).toEqual(expectedActions);
done();
})
});
You also need to modify your code for loadAllProjects so that the inner function returns the promise:
export const loadAllProjects = () => {
return (dispatch) => {
// You will need to return the promise in order for the test to be able to call .then() on it.
return getHeadersWithToken()
.then(applicationJsonHeaders => {
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
dispatch(setAllProjects(json))})
.catch(error => {
console.error(error)
dispatch(failedToLoadProjects(error))
});
})
}}
Also, as already stated, you must tell the jest when the test is done if you are testing async stuff. Do this by letting your it call take done as a param and call that as a function after you have verified the outcome i the .then()
This was just a quick fix from my side. There still might be something im missing or some bugs in the code above, but you get the point.
Let me know if you have any follow up questions?
In case this is ever useful to anyone and in acknowledgment of Septastium's answer, I eventually changed the code to:
async getHeadersWithToken(requestType) {
if (process.env.REACT_APP_RUNNING_LOCALLY==="true") {
return {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
};
}
let result = await fetch("/.auth/me", this.requestOptions)
let headers = result.json()
.then( json => {
const header = 'Bearer ' + json[0].access_token
const applicationJsonHeaders = this.getJsonHeaders(header, requestType)
return applicationJsonHeaders
})
.catch(error => {
console.error(error)
})
return headers
}
export const loadAllProjects = () => {
return async dispatch => {
const authenticator = new Authenticator()
let applicationJsonHeaders = await authenticator.getHeadersWithToken(constants.GET)
let loggedInUser = await authenticator.getLoggedInUser()
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return await fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(response => {
return parseResponseAndHandleErrors(response)
})
.then(json => dispatch(setAllProjects(json)))
.then(()=> dispatch(setAuthenticationData(loggedInUser)))
.catch(error => {
console.error(error)
return dispatch(failedToLoadProjects(error))
});
}
}
and the test to:
const checkActionsWereDispatched = async (expectedActions, actionCreator) => {
const store = mockStore(Map(
{
}),
);
store.dispatch(await actionCreator.apply()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
}
it('should create SET_ALL_PROJECTS action when fetching projects', async () => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
checkActionsWereDispatched(expectedActions, actions.loadAllProjects)
});
As noted above I think Spetastium's version of the test is easier to read than mine and his article here was very helpful.

Categories