I'm using react-native new version ( v: 0.62.2 ).
I take action on the page I created and go to another page with react-navigation
function Register({ route, navigation }) {
const [loading, setLoading] = useState(false);
const [name, setName] = useState('');
const _go = () => {
navigation.navigate('Home', { });
}
const _firebase = () => {
firebase.messaging().getToken(firebase.app().options.messagingSenderId).then(x => _token(x)).catch(e => console.log(e));
}
useEffect(() => {
_firebase();
return () => {
_go, _firebase
};
}, [navigation, loading, name]);
}
I call the _go function after doing the above code
What I want to ask is, is the page open when I go to another page with the _go function?
The following code is enough to close the page to improve performance
return () => {
_go, _firebase
};
}, [navigation, loading, name]);
componentWillUnmount is used when you are going to remove event listeners, timers and etc.
But you didn't declare such things in your code.
Anyway if you are going to implement cWU effect then you need to define it on componentDidMount like this
useEffect(() => {
return () {
// remove events and timers
}
}, []);
If you pass emptry array as second parameter it is same as componentDidMount.
Hope this helps you to understand.
In addition please refer to this article about lifeCycles - https://reactjs.org/docs/hooks-effect.html
Related
I have created a custom hook that fetches setting from an api that uses Async-Storage.
// Takes the key/name of the setting to retrieve
export const useSettings = (key) => {
// stores the json string result in the setting variable
const [setting, setSetting] = useState("");
const deviceStorage = useContext(DeviceStorageContext);
useEffect(() => {
getValue()
.then(value => setSetting(value));
}, []);
// gets value from my custom api that uses Async-Storage to handle r/w of the data.
const getValue = async () => await deviceStorage.getValueStored(key);
const setValue = async (value) => {
await deviceStorage.setValueStored(key, value);
getValue().then(value => setSetting(value));
};
const removeValue = async () => { }
return [setting, { setValue,removeValue }];
};
This works as expected in Main.jsx without any problem.
const Main = () => {
const [units, operations] = useSettings('units');
useEffect(() => {
const initSettings = async () => {
if (units) {
console.log(units)
return;
}
await operations.setValue({ pcs: 1, box: 20 });
};
initSettings();
}, []);
However, when I even just call the useSetting hook in Form.jsx and visit the page, it freezes my entire app to just that page.
const FormView = ({ handleReset, handleSubmit }) => {
const [setting,] = useSettings('units');
Removing the useState and useEffect fixes it and calling these methods directly works but I really don't want to call getValue() throughout my project and use async/await code to handle it.
Stuck on this for hours now. Any help will be appreciated.
Thank you.
It was a dropdown component library inside FormView that was messing it up. Removing that library fixed it.
I have the following case:
export default function Names() {
const dispatch = useDispatch();
const [names, setNames] = useState([]);
const stateNames = useSelector(state => state.names);
const fetchNames = async () => {
try {
const response = await nameService.getNames();
dispatch(initNames(response.body));
setNames(response.body);
} catch (error) {
console.error('Fetch Names: ', error);
}
};
useEffect(() => {
fetchNames();
}, []);
return (
{ names.map((name, index) => (
<Tab label={ budget.label} key={index}/>
)) }
);
}
When my component is rendered in the browser console I get a warning: "React Hook useEffect has a missing dependency: 'fetchBudgets'. Either include it or remove the dependency array react-hooks / exhaustive-deps".
If I comment the line in which I write the names in Redux state, the warning does not appear.
I need the list of names in the state so that I can update the list when a new name is written to the list from the outside.
export default function AddNameComponent() {
const dispatch = useDispatch();
const [label, setLabel] = useState('');
const [description, setDescription] = useState('');
const onLabelChange = (event) => { setLabel(event.target.value); };
const onDescriptionChange = (event) => { setDescription(event.target.value); };
const handleSubmit = async (event) => {
try {
event.preventDefault();
const newName = {
label: label
description: description
};
const answer = await budgetService.postNewName(newName);
dispatch(add(answer.body)); // Adding new Name in to Redux state.names
} catch (error) {
setErrorMessage(error.message);
console.error('Create Name: ', error);
}
};
return (
<div>
// Create name form
</div>
)
}
This is how everything works, but I don't understand why I have a warning.
I tried to add a flag to the array with dependencies of usеЕffect.
I tried to pass the function 'fetchNames' through the parent component - in props and to add it as a dependency, but it is executed twice ...
Can you advise please!
It's just an eslint warning so you don't have to fix it. But basically any variables which are used in the useEffect function are expected to be included in the dependency array. Otherwise, the effect will never be re-run even if the function fetchBudgets were to change.
It is expecting your hook to look like
useEffect(() => {
fetchBudgets();
}, [fetchBudgets]);
Where the effect will run once when the component is mounted and run again any time that the fetchBudgets function changes (which is probably never).
If it's executing more than once, that means that fetchBudgets has changed and you should try to figure our where and why it has been redefined. Maybe it needs to be memoized?
Here are the docs on putting functions in the dependency array.
Thanks for your attention! I tried many options and finally found one solution.
useEffect(() => {
async function fetchNames() {
const response = await nameService.getNames();
dispatch(init(response.body));
setNames(response.body);
}
fetchNames();
}, [dispatch, props]);
I put 'props' in an array of dependencies for one useEffect execution.
I'm trying to fetch data from an API and set a state with the data, but when I use the data in a child component, I get an [Unhandled promise rejection: TypeError: null is not an object (evaluating 'data.name')] warning.
Here is a gist of what I'm trying to do. Does anyone know why this might be occurring? I assume it's because the data isn't received from the API. I have tried adding an "isLoading" state and only returning the ChildComponent if it's false, but I still get the same warning (this might be because setNewProp in useEffect isn't updating when it receives the data from the API).
const ParentComponent = (props) => {
const [data, setData] = useState(null);
const [newProp, setNewProp] = useState();
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
useEffect(() => {
fetchData();
setNewProp({ data, ...props });
}, []);
return (
<ChildComponent newProp={newProp} />
);
}
You cannot use an async function inside an useEffect lifecycle event. As a good solution i would recommend to fully utilize the useEffect hook and use it as an effect to the updated data.
const ParentComponent = (props) => {
const [data, setData] = useState(null);
const [newProp, setNewProp] = useState();
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
setNewProp({ data, ...props });
}, [data]);
return (
<ChildComponent newProp={newProp} />
);
}
I also want to point out that useEffect runs AFTER the first render. That means your ChildComponent will always receive "undefined" as first props, since there is no initial value set at:
const [newProps, setNewProp] = useState(); // initial value comes here to prevent errors
Looks like maybe you have missed the await that is needed in useEffect() to make the code wait until that fetch has finished:
Before:
useEffect(() => {
fetchData();
setNewProp({ data, ...props });
}, []);
After:
useEffect(() => {
(async () => {
await fetchData();
setNewProp({ data, ...props });
})();
}, []);
Note that useEffect() doesn't support async functions (because it needs the return value to be a cleanup function, or undefined. For example, see this article.
BUT even better might be something like:
const [data, setData] = useState(null);
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
fetchData();
if (data) {
const newProp = { data, ...props };
}
In your code, you first call fetchData function, which calls a useState hook when it's done. Since useState hook works asynchronously, the state data will not be changed right after.
useEffect(() => {
fetchData(); // Called setData()
setNewProp({ data, ...props }); // At this point, data hasn't changed yet.
}, []);
So you can use useEffect hook again to watch for changes in your data state. Then you should set the new state of your newProp.
useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
useEffect(() => {
setNewProp({...props, data });
}, [data]);
I only want useEffect to run when my dependency list changes, it is also running every time the component is mounted, is there any way to not fire on mount?
You can tell React to skip applying an effect if certain values
haven’t changed between re-renders.
I initially thought that meant it shouldn't re-render on subsequent mounts but this question cleared that up.
I am displaying a list of records in a master "page" (react-router), the user can choose a single record and go to the detail page, and then return to the master page - so the master list component is completely unmounted/mounted in that scenario. And every time I load the "master page", I see the data being fetched, I only want this to happen when one of the dependencies changes; these dependencies and the data itself are stored in Redux so they're global.
Can useEffect or another hook be made to only fire when the dependencies change?
const {page, pageSize, search, sorts} = useSelector(getFilters);
const data = useSelector(getData);
useEffect(() => {
console.log("fetching");
dispatch(fetchData(page, pageSize, search, sorts));
}, [page, pageSize, search, sorts]);
You can't configure it out of the box.
But, a common pattern is to use some isMounted flag like so:
// Is Mounted
const useFetchNotOnMount = () => {
...
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) {
console.log('fetching');
dispatch(fetchData(filters));
} else {
isMounted.current = true;
}
}, [dispatch, filters]);
};
// Same (Is First Render)
const useFetchNotOnMount = () => {
...
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log("fetching");
dispatch(fetchData(filters));
}
}, [dispatch, filters]);
};
Read more in depth uses of useEffect
If you have several useEffect to prevent from running at initially, you can do the following:
export default function App() {
const mountedRef = useMountedRef();
const [isLoggedIn, setLoggedIn] = React.useState(false);
const [anotherOne, setAnotherOne] = React.useState(false);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", isLoggedIn);
}
}, [isLoggedIn]);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", anotherOne);
}
}, [anotherOne]);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", isLoggedIn, anotherOne);
}
}, [anotherOne, isLoggedIn]);
return (
<div>
<button onClick={() => setLoggedIn(true)}>Login</button>
</div>
);
}
const useMountedRef = () => {
const mountedRef = React.useRef(false);
React.useEffect(() => {
setTimeout(() => {
mountedRef.current = true;
});
}, []);
return mountedRef;
};
Demo: https://stackblitz.com/edit/react-eelqp2
One thing important is that you have to use setTimeout to make a reasonable delay to make sure that the ref value is set to true after all initial useEffects.
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
useEffectAfterMount(() => {
console.log("fetching");
dispatch(fetchData(page, pageSize, search, sorts));
}, [page, pageSize, search, sorts]);
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
I know this is late to the game but I think it's also worth noting that for what the OP is trying to accomplish, data caching would be a more wholistic and scalable solution. Libraries like react-query are great for this.
I get 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.
when fetching of data is started and component was unmounted, but function is trying to update state of unmounted component.
What is the best way to solve this?
CodePen example.
default function Test() {
const [notSeenAmount, setNotSeenAmount] = useState(false)
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [])
async function updateNotSeenAmount() {
let data // here i fetch data
setNotSeenAmount(data) // here is problem. If component was unmounted, i get error.
}
async function anotherFunction() {
updateNotSeenAmount() //it can trigger update too
}
return <button onClick={updateNotSeenAmount}>Push me</button> //update can be triggered manually
}
The easiest solution is to use a local variable that keeps track of whether the component is mounted or not. This is a common pattern with the class based approach. Here is an example that implement it with hooks:
function Example() {
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
let isCancelled = false;
simulateSlowNetworkRequest().then(() => {
if (!isCancelled) {
setText("done!");
}
});
return () => {
isCancelled = true;
};
}, []);
return <h2>{text}</h2>;
}
Here is an alternative with useRef (see below). Note that with a list of dependencies this solution won't work. The value of the ref will stay true after the first render. In that case the first solution is more appropriate.
function Example() {
const isCancelled = React.useRef(false);
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
fetch();
return () => {
isCancelled.current = true;
};
}, []);
function fetch() {
simulateSlowNetworkRequest().then(() => {
if (!isCancelled.current) {
setText("done!");
}
});
}
return <h2>{text}</h2>;
}
You can find more information about this pattern inside this article. Here is an issue inside the React project on GitHub that showcase this solution.
If you are fetching data from axios(using hooks) and the error still occurs, just wrap the setter inside the condition
let isRendered = useRef(false);
useEffect(() => {
isRendered = true;
axios
.get("/sample/api")
.then(res => {
if (isRendered) {
setState(res.data);
}
return null;
})
.catch(err => console.log(err));
return () => {
isRendered = false;
};
}, []);
TL;DR
Here is a CodeSandBox example
The other answers work of course, I just wanted to share a solution I came up with.
I built this hook that works just like React's useState, but will only setState if the component is mounted. I find it more elegant because you don't have to mess arround with an isMounted variable in your component !
Installation :
npm install use-state-if-mounted
Usage :
const [count, setCount] = useStateIfMounted(0);
You can find more advanced documentation on the npm page of the hook.
Here is a simple solution for this. This warning is due to when we do some fetch request while that request is in the background (because some requests take some time.)and we navigate back from that screen then react cannot update the state. here is the example code for this. write this line before every state Update.
if(!isScreenMounted.current) return;
Here is Complete Example
import React , {useRef} from 'react'
import { Text,StatusBar,SafeAreaView,ScrollView, StyleSheet } from 'react-native'
import BASEURL from '../constants/BaseURL';
const SearchScreen = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
return () => isScreenMounted.current = false
},[])
const ConvertFileSubmit = () => {
if(!isScreenMounted.current) return;
setUpLoading(true)
var formdata = new FormData();
var file = {
uri: `file://${route.params.selectedfiles[0].uri}`,
type:`${route.params.selectedfiles[0].minetype}`,
name:`${route.params.selectedfiles[0].displayname}`,
};
formdata.append("file",file);
fetch(`${BASEURL}/UploadFile`, {
method: 'POST',
body: formdata,
redirect: 'manual'
}).then(response => response.json())
.then(result => {
if(!isScreenMounted.current) return;
setUpLoading(false)
}).catch(error => {
console.log('error', error)
});
}
return(
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Text>Search Screen</Text>
</ScrollView>
</SafeAreaView>
</>
)
}
export default SearchScreen;
const styles = StyleSheet.create({
scrollView: {
backgroundColor:"red",
},
container:{
flex:1,
justifyContent:"center",
alignItems:"center"
}
})
This answer is not related to the specific question but I got the same 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. and as a React newcomer could not find a solution to it.
My problem was related to useState in an unmounted component.
I noticed that I was calling a set state function (setIsLoading) after the function that unmounted my component:
const Login = () => {
const [isLoading, setIsLoading] = useState(false);
const handleLogin = () => {
setIsLoading(true);
firebase.auth().then(
functionToUnMountLoginSection();
// the problem is here
setIsLoading(false);
)
}
}
The correct way is to call setIsLoading when the component is still mounted, before calling the function to unmount/process user login in my specific case:
firebase.auth().then(
setIsLoading(false);
functionToUnMountLoginSection();
)
You add the state related datas into the useEffect body for not rerunning them every rerendering process. This method will solve the problem.
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [notSeenAmount])
REF: Tip: Optimizing Performance by Skipping Effects
Custom Hook Solution (ReactJs/NextJs)
Create a new folder named 'shared' and add two folders named 'hooks', 'utils' in it. Add a new file called 'commonFunctions.js' inside utils folder and add the code snippet below.
export const promisify = (fn) => {
return new Promise((resolve, reject) => {
fn
.then(response => resolve(response))
.catch(error => reject(error));
});
};
Add a new file called 'fetch-hook.js' inside hooks folder and add the code snippet below.
import { useCallback, useEffect, useRef } from "react";
import { promisify } from "../utils/commonFunctions";
export const useFetch = () => {
const isUnmounted = useRef(false);
useEffect(() => {
isUnmounted.current = false;
return () => {
isUnmounted.current = true;
};
}, []);
const call = useCallback((fn, onSuccess, onError = null) => {
promisify(fn).then(response => {
console.group('useFetch Hook response', response);
if (!isUnmounted.current) {
console.log('updating state..');
onSuccess(response.data);
}
else
console.log('aborted state update!');
console.groupEnd();
}).catch(error => {
console.log("useFetch Hook error", error);
if (!isUnmounted.current)
if (onError)
onError(error);
});
}, []);
return { call }
};
Folder Structure
Our custom hook is now ready. We use it in our component like below
const OurComponent = (props) => {
//..
const [subscriptions, setSubscriptions] = useState<any>([]);
//..
const { call } = useFetch();
// example method, change with your own
const getSubscriptions = useCallback(async () => {
call(
payment.companySubscriptions(userId), // example api call, change with your own
(data) => setSubscriptions(data),
);
}, [userId]);
//..
const updateSubscriptions = useCallback(async () => {
setTimeout(async () => {
await getSubscriptions();
}, 5000);// 5 seconds delay
}, [getSubscriptions]);
//..
}
In our component, we call 'updateSubscriptions' method. It will trigger 'getSubscriptions' method in which we used our custom hook. If we try to navigate to a different page after calling updateSubscriptions method before 5 seconds over, our custom hook will abort state update and prevent that warning on the title of this question
Wanna see opposite?
Change 'getSubscriptions' method with the one below
const getSubscriptions = useCallback(async () => {
const response = await payment.companySubscriptions(userId);
setSubscriptions(response);
}, [userId]);
Now try to call 'updateSubscriptions' method and navigate to a different page before 5 seconds over
Try this custom hook:
import { useEffect, useRef } from 'react';
export const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
function Example() {
const isMounted = useIsMounted();
const [text, setText] = useState();
const safeSetState = useCallback((callback, ...args) => {
if (isMounted.current) {
callback(...args);
}
}, []);
useEffect(() => {
safeSetState(setText, 'Hello')
});
}, []);
return <h2>{text}</h2>;
}