React won't let me use `useEffect` in a completely reasonable way - reactjs

I created the following helper functions because functional components in React do not have mount and unmount events. I don't care what people say; useEffect is not a an equivalent. It can be as I demonstrate below:
//eslint-disable-next-line
export const useMount = callback => useEffect(callback, []);
//eslint-disable-next-line
export const useUnmount = callback => useEffect(() => callback, []);
React does not let me do this because I am technically calling useEffect from a non-component function. I'm doing this because when I use useEffect as a mount or unmount event, it pollutes my terminal with meaningless warnings about not including something in the dependency list. I know, I should be doing this...
export default function MusicPlayback(...) {
...
useEffect(() => stopMusic, []);
...
}
But then I get a warning about stopMusic not being included as a dependency. But I don't want it to be a dependency because then useEffect will no longer be an unmount event and stopMusic will be called on every render.
I know that it is eslint that is warning me and I can use //eslint-disable-next-line but that is too ugly to have in every single file that needs an unmount handler.
To my knowledge there is no way to have an unmount handler without using //eslint-disable-next-line absolutely everywhere. Is there some way around this?

Ok, the dependency check is there for a reason, even when you think it shouldn't be there.
useEffect(() => {
stopMusic()
...
}, [stopMusic, ...])
Let's talk about stopMusic, suppose this is a global function from another third party. If the instance never changes, then you should fire it as a dependency, since it won't hurt.
And if the stopMusic instance does change, then you need to ask yourself why you don't want to put it as a dependency, because it might be accidentally calling an old stopMusic.
Now, suppose you are good with all these and still don't want it to be wired with stopMusic, then consider use a ref.
const ref = useRef({ stopMusic })
useEffect(() => ref.current.stopMusic(), [ref])
Either way you get the point, it has to depend on something, maybe your business logic doesn't want to. But technically as long as you need to invoke something which isn't part of the useEffect, it needs to be a dependency. Otherwise from the useEffect perspective, it's an out-of-sync issue. The point of ref (or any object) is to get into this out-of-sync deliberately.
Of course, if you really hate this linter rule, i believe you can disable it.
NOTE
React community is proposing a way in the future to add these dependencies for you behind your back. The rational behind it is that React is designed to be reactive to the data in one-way train.

This is what I ended up having to do to stop the music on unmount.
export default function MusicPlayback(...) {
const [playMusic, stopMusic] = useMagicalSoundHookThingy(myMusic);
const stopMusicRef = useRef(stopMusic);
stopMusicRef.current = stopMusic; // Gotta do this because stopMusic no longer works after render.
...
useEffect(() => {
const stopMusicInHere = stopMusicRef.current; // Doing this to avoid a warning telling me that when called the ref will probably no longer be around.
return stopMusicInHere;
}, [stopMusicRef]);
...
}
Using a ref like this isn't meaningful. It is just a clever hack to fool eslint. We are packaging something that changes on every render into something that doesn't. That's all we are doing.
The problem I am having is that the entity I'm interacting with is static. But the means to communicate with that entity (namely the function stopMusic) is transient. So the brute force means by which useEffect determines dependence isn't nuanced enough to really indicate whether some dependency has actually changed. Just the tiddlywinks that invoke that dependency, the functions and object created by the hooks. Perhaps this is the fault of the hook writer. Maybe the tiddlywinks should maintain the same life cycle as the entity.
I love React very much, but this is an annoyance I've had for a while, and I'm tired of people telling me that I should just include all the dependencies eslint demands as if I don't really understand what dependencies are actually involved. It is probably ideal to never have any side effects at all in a React program, and rely on a data repository pipeline like Redux to provide any context. But this is the real world and there will always be entities with disconnected lifecycles. Music playing in the background is one such entity. Such is life.

Related

Using Mobx observables with React dependencies

Mobx docs says to use pattern useEffect(reaction(..)) to track observable changes, which looks like some cross-breeding to me. Whats the problem of using react dependencies array to achieve that? I did a basic test, and it works as intended:
const Hello = observer(() => {
const {
participantStore: { audioDisabled },
} = useStores();
useEffect(() => {
console.log('changed', audioDisabled);
}, [audioDisabled]);
return <h1>TEST ME</h1>;
});
There is absolutely no problem with using React things like useEffect, useMemo and etc with MobX. You just need to list all dependencies and if there are many of them maybe it is easier to use reaction or autorun.
So feel free to use whatever way you like more.
Mixing MobX observables and hooks can be mostly painless, but there are a few things to look out for. The most common thing to watch out for is that adding observable values to the dependencies array will not always trigger the effect. Under the surface, MobX objects and arrays are stable references to proxy objects. So even though the value of the observable changes, the useEffect hook doesn't always pick up on that change.
The solution is simple, and is outlined in the MobX Docs under React Integration: useEffect and observables:
Using useEffect requires specifying dependencies. With MobX that isn't really needed, since MobX has already a way to automatically determine the dependencies of an effect, autorun. Combining autorun and coupling it to the life-cycle of the component using useEffect is luckily straightforward
In practice, this looks like:
const Hello = observer(() => {
const { participantStore } = useStores();
useEffect(() => autorun(() => {
console.log('changed', participantStore.audioDisabled);
}), []);
return <h1>Audio is {!participantStore.audioDisabled && 'NOT '}Disabled</h1>;
});
There are a few things to be mindful of:
If you are also dealing with local state (via a useState or other hook), you will need to include those in the dependencies array.
Be sure to return the autorun function from your useEffect hook. This ensures that the listener is de-registered on component unmount.
Your linter will likely get frustrated with missing dependencies. I won't make a recommendation either way since there are pros and cons to either disabling it globally, or keeping it enabled and disabling it per-line. It largely depends on how much local state you will be dealing with.

React, ESLint: eslint-plugin-react-hooks shows incorrect "missing dependency"

Assume you are using React and you are writing a custom hook useSomething that returns the identical same thing each time it is invoked for the same component.
const something = useSomething()
// useSomething() at time X === useSomething() at time Y
If you now use this something value inside of a useEffect(() => ...) and you do not pass something as a dependency to the array of the second argument of useEffect then the linter will warn you:
React Hook useEffect has a missing dependency: 'something'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Of course ESLint cannot know that something will always stay identical (per component), but adding not-changing things like something to the dependency array of useEffect each time they are used is really annoying. Just deactivating react-hooks/exhaustive-deps does also not seem to be a good solution (nor using // eslint-disable-next-line react-hooks/exhaustive-deps).
Is there a better solution than to add things like that unnecessarily to the dependency array of useEffect just to make the Linter happy?
Please find a simple demo here:
https://codesandbox.io/s/sad-kowalevski-yfxcn [Edit: Please be aware that the problem is about the general pattern described above and not about this stupid little demo - the purpose of this demo is just to show the ESLint warning, nothing else]
[Edit] Please find an additional demo here:
https://codesandbox.io/s/vibrant-tree-0cyn1
Here
https://github.com/facebook/react/issues/14920#issuecomment-471070149
for example you can read this:
If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.
So maybe just adding that never-changing values to the dependency array of useEffect may yet be the best solution. Nevertheless I hoped there would be something like a ESLint react-hooks configuration possibility to define a list of hook names which whose return values should be considered as static.
The example is a little contrived but I suspect you may wish to create a new useEffect block without this dependency.
If the store is not changing though I'd question why you'd wish to console log it time. If you wish to log it only on change then you'd add someStore to your dependency array. It really depends on what you're trying to achieve and your seperation of concerns.
I'd argue that if someStore is used as part of whatever logic is handled in this effect then it does belong in your dependency array.
You could also alternatively move const something = useSomething() into your effect and extract it as a custom hook link
useEffect(() => {
console.log("Current state (may change)", someState);
}, [someState]);
useEffect(() => {
console.log("Current store (will never change)", someStore);
});

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.

React hooks appends unwanted value with eslint-plugin-react-hooks#next

I have a useEffect hook in my component which call a Redux action to fetch some data.
useEffect(
() => {
props.getClientSummaryAction();
},[]
);
When I go to save the file the linter does this.
useEffect(
() => {
props.getClientSummaryAction();
}, [props]
);
Which obviously send my component into an infinite loop as getClientSummaryAction fetches some data which updates the props.
I have used deconstruction like this and the linter updates the array.
const { getClientSummaryAction } = props;
useEffect(
() => {
getClientSummaryAction();
},
[getClientSummaryAction]
);
Which I am fine with but it doesn't really make sense because getClientSummaryAction will obviously never be updated, its a function.
I just want to know why the linter is doing this and whether this is best practice.
It's not unwanted. You know for a fact that the dependency is not going to change, but React can possibly know that. The rule is pretty clear:
Either pass an array containing all dependencies or don't pass anything to the second argument and let React keep track of changes.
Pass the second argument to useEffect is to tell React that you are in charge of hook's call. So when you don't pass a dependency that is included inside your effect eslint will see this as a possible memory leak and will warn you. DO NOT disable the rule just continue to pass the dependency as you are already doing. May feel redundant but it's for the best.

What is the proper way to duplicate componentDidMount with hooks?

I'm having trouble understanding the proper way to recreate the behavior of the componentDidMount life cycle function using react hooks.
I have found the generally accepted method is like so:
useEffect(() => {
//do componentDidMount stuff here
}, []);
However, when theres additional parameters, other dependencies, etc. I get linting errors, as in this example:
useEffect(() => {
fetchData(design, onSuccess, onError);
}, []);
That one throws linting errors. What would be the proper way to handle that type of scenario? I'd like to avoid disabling eslint.
React Hook useEffect has missing dependencies: 'design' and 'onSuccess'. Either include them or remove the dependency array react-hooks/exhaustive-deps
You can take a look at this issue. I found it very interesting.
You can also take a look at Two Ways to Be Honest About Dependencies that Aron mentions on his answer. It's very interesting and goot to understand hooks dependencies.
I'd like to avoid disabling eslint.
So to do that, here is what you need to do.
In the issue, some one gives an example where he calls a function from outside of the useEffect.
const hideSelf = () => {
// In our case, this simply dispatches a Redux action
};
// Automatically hide the notification
useEffect(() => {
setTimeout(() => {
hideSelf();
}, 15000);
}, []);
And by reading all the comments and looking at Dan Abramov comment
... But in this specific example the idiomatic solution is to put hideSelf inside the effect
So this means doing
// Automatically hide the notification
useEffect(() => {
const hideSelf = () => {
// In our case, this simply dispatches a Redux action
};
setTimeout(() => {
hideSelf();
}, 15000);
}, []);
This an example where you can solve the problem without using disableling eslint.
If this isn't your case (maybe you use Redux or something alike) you should put it as a deppendency of the effect
... If it dispatches a Redux action then put this action as a dependency
To solve this problem, it deppends alot of your situation. You didn't give us a clear example of what is your case, so I found one an give you a generic solution.
Short answer
Add everything that is outside of the effect in the effect dependency (inside [])
OR
Declare the functions that are outside of the effect inside of it.
I'm guessing you're getting the exhaustive-deps error?
When using a useEffect the recommendation is to put all values that are used in the effect in the dependencies array, so that you are being "honest" about which values the effect uses. Dan Abramov talks about this here https://overreacted.io/a-complete-guide-to-useeffect/#two-ways-to-be-honest-about-dependencies.
However if you are happy to ignore this and are sure that you only want this effect to run the first time this component renders then you can safely ignore the lint errors using // eslint-disable-line exhaustive-deps.
EDIT: There isn't really a way round this because ultimately you are not being "honest" about your deps, strictly speaking.

Resources