useEffect as dependency array or trigger array? - reactjs

I am having trouble knowing how the useEffect dependency array rules work.
By the docs I see that it tells me, we should include all the props/state/derived vars that are used inside the useEffect.
So let's say we want to execute some code when the param count changes, but access the innerCount value as well. By the docs it states I should add it to the dependencies list to prevent the code from having stale values from previous renders, but technically it will always get the correct value even if I don't pass it in the dependency list...
And also if I do add it to the dependency list, that means I would have to do double validation inside the useEffect just to run that code ONLY when count has changed from its previous value...
function Component({ count }) {
const [innerCount, setInnerCount] = useState(0);
useEffect(() => {
console.log('innerCount', innerCount);
setInnerCount(innerCount + 1);
}, [count]);
return <div>
<span>Count: {count}</span>
<span>innerCount: {innerCount}</span>
</div>;
}
My point is, should I follow react docs and always include all dependencies of the useEffect which adds a lot of complexity? or just ignore it for these cases when stale values will not happen?

You should always include it, but I know it can be annoying sometimes.

Related

React calling a method on load only once

I'm new to Hooks and have been encountering some cases that have been sending me chasing my tail.
Hope someone can explain or provide solutions that make sense to me:
Loading a method only once on mount of a component is confusing. I tried this method, it works, but don't understand it. Can someone explain this to me:
const useMountEffect = fun => useEffect(fun, [])
useMountEffect(() => {
if (!isEdit) handleAddRow()
})
const handleAddRow = () => {
let appendArr = [...listArr, '']
setListArr(appendArr)
}
Following this thread:
How to call loading function with React useEffect only once
I tried just using useEffect without dependency and eslint does not like that and they recommend adding a skip next line which seems kind of hacky:
// eslint-disable-next-line
If I'm correct you want something similar to componentDidMount life-cycle method.
The way to do that is
function MyComponent(props){
useEffect(()=>{
// do stuff here...
}, []) // <-- empty dependency array
return <div></div>
}
To understand what's happening you'll need to understand how the useEffect hooks work.
Using the useEffect hook without any dependencies will trigger the effect every time some state or prop changes and causes a re-render.
but if we pass an empty array as a dependency it will be as the effect not dependent on anything else, so it will only trigger when the component mounts.
The empty array [] argument tells useEffect() to run only once
It will run only once on mount, like componentDidMount used to
Explanation:
useEffect() takes 2 arguments - your function and an array of dependencies
useEffect(
yourFunction, // <- function that will run on every dependency update
[] // <-- empty dependency array
)
The dependency array argument tells it when to run your function. If the dependencies don't change it won't run again. If dependencies do update it will run your function again.
If you pass in an empty array (no dependencies) then the function will only run on mount.
ESLint missing dependency error
React Hook useEffect has a missing dependency: 'xxx'. Either include it or remove the dependency array react-hooks/exhaustive-deps
The reason for the ESLint rule is that useEffect() is designed to run on every dependency update it will expect you to add all the dependencies to that array.
To ignore this you can add above the dependency array line:
// eslint-disable-next-line react-hooks/exhaustive-deps
For anyone looking to execute a function once per whole App (globally), this could be a solution:
if (typeof window !== 'undefined') { // Check if we're running in the browser.
// ✅ Only runs once per app load
checkAuthToken();
loadDataFromLocalStorage();
}
function App() {
// ...
}
Source / extended explanation here.

Correct/Incorrect to ignore some React useEffect dependency warnings?

Here's some sample code I've written that works fine:
useEffect(() => {
if (!rolesIsLoading && rolesStatus === 200) {
customer.roles = rolesData.roles;
}
}, [rolesIsLoading, rolesStatus]);
I'm getting this warning in the console:
React Hook useEffect has missing dependencies: 'customer.roles' and 'rolesData.roles'. Either include them or remove the dependency array react-hooks/exhaustive-deps
The thing is, the code works fine now and, in some cases, when I add such dependencies, as instructed, I end up with an endless loop or some other problem.
I'd appreciate any advice you might have as to how you resolved this when you've encountered a similar situation.
From React DOCS:
Conditionally firing an effect
The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes.
However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source props has changed.
To implement this, pass a second argument to useEffect that is the array of values that the effect depends on. Our updated example now looks like this:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
So, from the exercpt above we see that the dependencies array serves the purpose to conditionally fire an effect.
That warning you're getting is because you're using some external (from the effect's perspective) variables in your effect function that you're not mentioning in the dependency array.
React is worried that you might be getting new values for those variables in future renders, and since your effect uses them, React "default intent" is to re-run that effect with the new variables values. So your effect would always be up to date.
So you need to think if you want to re-run your effect if/when those variables change in the future.
Basic recommendations:
Add them to the dependency array
If they never change, there will be no difference
If they change, add some conditionals to your effect to decide whether you should do something based on the new variables values or not
If they're changing only in reference, but not in value (like functions, arrays, objects, etc), you can use the useCallback or useRef hook to preserve the "reference" for those variables from the first render and not get new references on every render.

useEffect pushing value to array only once ReactJs

So basically i started learning ReactJs and am trying to build a chatting app
now here there is section of adding Channels so here is my code
const [Channels, setChannelState] = React.useState([])
let loadedChannels = []
React.useEffect(() => {
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
console.log(loadedChannels)
setChannelState(loadedChannels)
})
},[])
so here when i tried to log the loadedChannels Array am getting all the result but when i try to set this array to my state then am getting only one result
how can i resolve this?
You have an empty array [] as a second parameter which runs the function passed to useEffect only once.
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect which you have done.
If we pass [] as second parameter this will apply effect only once.
We can pass value inside array, only on change which it will trigger useEffect call.
As per the documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
try using something like this:
const [Channels, setChannelState] = React.useState([])
React.useEffect(() => {
let loadedChannels = []
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
})
setChannelState([...loadedChannels])
},[])
Hope this helps, Happy coding!!!
The second argument for useEffect is called a dependency array. And when it's empty, it acts like componentDidMount() i.e it runs only once, after the first render.
You also need to be careful about setting state in useEffect, since this might create an infinite loop of re-renders if not done correctly - setCurrentUser. That is not enough code to help any further than that.
This is the perfect time to get into React. Have fun!
Second parameter is passed for conditional rendering. To understand that there are few scenarios which tell us the possible ways to use that.
There are multiple scenarios which will make it easier for you to understand the importance and functionality of the second parameter with the help of the example from demo.
Scenario 1
Second parameter as empty array,
useEffect(() => {
console.log(`You clicked ${count} times`);
},
[ ]) //called only once similar to componentDidMount
If we pass an empty array, it means that we are not interested in monitoring any of the values so that the useEffect won’t be called except on mounting and before un-mounting. This mimic’s the working of componentDidMount and componentWillUnmount, it will only run once.
Scenario 2
Second parameter with value(s),
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); //function inside useEffect will be called when "count" will be updated
The value passed as the second parameter (array) here in our example: count will be responsible for the execution of the function inside the useEffect Hook. If the value inside the array will be updated, then and only then the function will be executed.
What if we have multiple useEffect hooks and we need to update just few of them? Conditional rendering comes in light. Conditional rendering is achieved by passing the second parameter instead of just empty array. The values in the array will be monitored and the useEffect function will only be called when the monitored value(s) is/are updated.
Refer these links for more info
https://stackblitz.com/edit/react-hooks-intro
https://www.c-sharpcorner.com/article/react-hooks-introduction/

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

React hooks: how to access props within "mount" useEffect & not throw linting warning

Unless I'm mistaken, this is valid code:
useEffect(() => {
if (prop1) {
doSomething();
}
}, []);
(prop1 is a prop). But when linting I get the following error:
React Hook useEffect has a missing dependency: 'prop1'. Either include it or remove the dependency array.
(react-hooks/exhaustive-deps)
I don't want to pass prop1 as a dependency because I would lose the "only run on mount" behaviour. But I need to access the prop to doSomething().
Any suggestions?
Hooks were new when this question was written, so maybe you already know this, but in case you or someone else wants to know:
React thinks that because your effect uses the value of prop1, it "depends" on prop1 and should be re-run whenever it changes. That's why the linter is complaining that it's not listed as a dependency.
However because you want the effect to only run "on mount", you want it to use the value of prop1 from the initial/first render, and never run again even if prop1 changes. This is at odds with the conceptual idea that the array lists all the variables the effect depends on, which is what the linter is focused on.
The solution alluded to in the React Hooks FAQ is to use useRef to keep track of whether or not this is the first render (edited):
const firstRenderRef = useRef(true)
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
doSomething(prop1)
}
}, [prop1])
This solution satisfies the linter, specifically because it follows the idea of listing all dependencies of the effect in the array. The ref allows the effect to also depend on a variable for whether this is the first render or not, but without rerendering when the value changes.
You could probably raise this here.. [ESLint] Feedback for 'exhaustive-deps' lint rule
Though I get the feeling where this is a case where you should add an eslint "ignore" comment if you are sure you don't want the effect to run on update of prop1.
A legit case for relaxing the warning was raised here..
https://github.com/facebook/react/issues/14920#issuecomment-467896512
Also check what version of the plugin you are running..
https://github.com/facebook/react/issues/14920#issuecomment-468801699
try this code
const usemount = ( functionToDoSomeThing, data ) => {
useEffect( () => {
functionToDoSomeThing( data );
},[] );
};
usemount( console.log, props );
i define a function to do Something and pass it to hook
in example i use console.log function

Resources