Is there any way to know whether the strict mode is enabled in React?
useEffect(() => {
if(strictModeEnabled) {}
})
I didn't find any information in the docs.
You can use this
export const useEffectOnce = ( effect )=> {
const destroyFunc = useRef();
const effectCalled = useRef(false);
const renderAfterCalled = useRef(false);
const [val, setVal] = useState(0);
if (effectCalled.current) {
renderAfterCalled.current = true;
}
useEffect( ()=> {
// only execute the effect first time around
if (!effectCalled.current) {
destroyFunc.current = effect();
effectCalled.current = true;
}
// this forces one render after the effect is run
setVal(val => val + 1);
return ()=> {
// if the comp didn't render since the useEffect was called,
// we know it's the dummy React cycle
if (!renderAfterCalled.current) { return; }
if (destroyFunc.current) { destroyFunc.current(); }
};
}, []);
};
call it by
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Read more here
Related
I have some functional component. Inside component I get value from redux store (I am using redux-toolkit). Also I have handler inside this component.
The value of variable from store set after request to api via RTK Query. So, the variable first has a default value, and then changes to value from the api.
Problem:
The value of variable from redux store doesn't updated inside handler.
const SomeContainer = () => {
const dispatch = useDispatch();
const variableFromStore = useSelector(someSelectors.variableFromStore);
console.log(variableFromStore) **// correct value (updated)**
const handleSomeAction = () => {
console.log(variableFromStore) **// default value of init store (not updated)**
};
return <SomeComponent onSomeAction={handleSomeAction} />;
};
SomeComponent
const SomeComponent = (props) => {
const { list, onSomeAction } = props;
const moreRef = useRef(null);
const loadMore = () => {
if (moreRef.current) {
const scrollMorePosition = moreRef.current.getBoundingClientRect().bottom;
if (scrollMorePosition <= window.innerHeight) {
onSomeAction(); // Call handler from Container
}
}
};
useEffect(() => {
window.addEventListener('scroll', loadMore);
return () => {
window.removeEventListener('scroll', loadMore);
};
}, []);
return (
...
);
};
How is it possible? What do I not understand?)
The problem is you're unintentionally creating a closure around the original version of handleSomeAction:
useEffect(() => {
window.addEventListener('scroll', loadMore);
return () => {
window.removeEventListener('scroll', loadMore);
}
}, []);
The dependencies array here is empty, which means this effect only runs the first time that your component mounts, hence capturing the value of loadMore at the time the component mounts (which itself captures the value of onSomeAction at the time the component mounts).
The "easy fix" is to specify loadMore as a dependency for your effect:
useEffect(() => {
window.addEventListener('scroll', loadMore);
return () => {
window.removeEventListener('scroll', loadMore);
}
}, [loadMore]);
BUT! This will now create a new problem - handleSomeAction is recreated on every render, so your effect will now also run on every render!
So, without knowing more details about what you're actually trying to do, I'd use a ref to store a reference to the onSomeAction, and the inline the loadMore into your effect:
// A simple custom hook that updates a ref to whatever the latest value was passed
const useLatest = (value) => {
const ref = useRef();
ref.current = value;
return ref;
}
const SomeComponent = (props) => {
const { list, onSomeAction } = props;
const moreRef = useRef(null);
const onSomeActionRef = useLatest(onSomeAction);
useEffect(() => {
const loadMore = () => {
if (!moreRef.current) return;
const scrollMorePosition = moreRef.current.getBoundingClientRect().bottom;
if (scrollMorePosition <= window.innerHeight) {
onSomeActionRef.current();
}
}
window.addEventListener('scroll', loadMore);
return () => window.removeEventListener('scroll', loadMore);
}, []);
return (
...
);
};
That's the warning in the console,
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.
Here is my code
const [index, setIndex] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const refContainer: any = useRef();
const [selectedIndex, setSelectedIndex] = useState(0);
const navigation = useNavigation();
useEffect(() => {
refContainer.current.scrollToIndex({animated: true, index});
}, [index]);
const theNext = (index: number) => {
if (index < departments.length - 1) {
setIndex(index + 1);
setSelectedIndex(index + 1);
}
};
setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
const onRefresh = () => {
if (refreshing === false) {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}
};
What should I do to make clean up?
I tried to do many things but the warning doesn't disappear
setTimeout need to use in useEffect instead. And add clear timeout in return
useEffect(() => {
const timeOut = setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
return () => {
if (timeOut) {
clearTimeout(timeOut);
}
};
}, []);
Here is a simple solution. first of all, you have to remove all the timers like this.
useEffect(() => {
return () => remover timers here ;
},[])
and put this
import React, { useEffect,useRef, useState } from 'react'
const Example = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
isScreenMounted.current = true
return () => isScreenMounted.current = false
},[])
const somefunction = () => {
// put this statement before every state update and you will never get that earrning
if(!isScreenMounted.current) return;
/// put here state update function
}
return null
}
export default Example;
const handleClick = () => {
setState({ ...state, on: !state.on });
let tog = state.on;
console.log("first" + tog);
const interval = setInterval(() => {
if (tog) {
console.log(tog);
} else clearInterval(interval);
}, 1000);
};
enter image description here
this one will not be able to stop even the tog is false;
however if I don't use state, change to a variable it will not happen,
it is so weird for me, I need some help;
let flag = true;
const handleClick = () => {
flag = !flag;
console.log("first" + flag);
const interval = setInterval(()=>{
if(flag){
console.log(flag);
}else(clearInterval(interval))
},1000)
};
React will create new handleClick on every re-renders, and there will be different setIntervals,
const intvl = useRef(null);
const handleClick = () => {
intvl.current = setInterval(() => { //now interval is not changing on every fn recreation
.....
clearInterval(intvl.current);
}
check this one
You should use an Effect to handle intervals, something like this:
useEffect(() => {
let intervalId
if (state.on) {
intervalId = setInterval(() => {
console.log(state.on)
}, 1000)
return () => clearInterval(intervalId)
}
}, [state.on])
const handleClick = () => {
setState({ ...state, on: !state.on });
};
working code
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'm learning to React Hooks.
And I'm struggling initialize data that I fetched from a server using a custom hook.
I think I'm using hooks wrong.
My code is below.
const useFetchLocation = () => {
const [currentLocation, setCurrentLocation] = useState([]);
const getCurrentLocation = (ignore) => {
...
};
useEffect(() => {
let ignore = false;
getCurrentLocation(ignore);
return () => { ignore = true; }
}, []);
return {currentLocation};
};
const useFetch = (coords) => {
console.log(coords);
const [stores, setStores] = useState([]);
const fetchData = (coords, ignore) => {
axios.get(`${URL}`)
.then(res => {
if (!ignore) {
setStores(res.data.results);
}
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
let ignore = false;
fetchData(ignore);
return () => {
ignore = true;
};
}, [coords]);
return {stores};
}
const App = () => {
const {currentLocation} = useFetchLocation();
const {stores} = useFetch(currentLocation); // it doesn't know what currentLocation is.
...
Obviously, it doesn't work synchronously.
However, I believe there's the correct way to do so.
In this case, what should I do?
I would appreciate if you give me any ideas.
Thank you.
Not sure what all the ignore variables are about, but you can just check in your effect if coords is set. Only when coords is set you should make the axios request.
const useFetchLocation = () => {
// Start out with null instead of an empty array, this makes is easier to check later on
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
// Somehow figure out the current location and store it in the state
setTimeout(() => {
setCurrentLocation({ lat: 1, lng: 2 });
}, 500);
};
useEffect(() => {
getCurrentLocation();
}, []);
return { currentLocation };
};
const useFetch = coords => {
const [stores, setStores] = useState([]);
const fetchData = coords => {
console.log("make some HTTP request using coords:", coords);
setTimeout(() => {
console.log("pretending to receive data");
setStores([{ id: 1, name: "Store 1" }]);
}, 500);
};
useEffect(() => {
/*
* When the location is set from useFetchLocation the useFetch code is
* also triggered again. The first time coords is null so the fetchData code
* will not be executed. Then, when the coords is set to an actual object
* containing coordinates, the fetchData code will execute.
*/
if (coords) {
fetchData(coords);
}
}, [coords]);
return { stores };
};
function App() {
const { currentLocation } = useFetchLocation();
const { stores } = useFetch(currentLocation);
return (
<div className="App">
<ul>
{stores.map(store => (
<li key={store.id}>{store.name}</li>
))}
</ul>
</div>
);
}
Working sandbox (without the comments) https://codesandbox.io/embed/eager-elion-0ki0v