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

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.

Related

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

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.

Best practice for indicating prop is used in useEffect or similar

Often when writing a component in React+Typescript, I want to trigger a useEffect hook on one of the props. For example, consider a button that executes a different function for each consecutive mouse click:
export function ActionButton({clickActions}: {clickActions: (() => void)[]}) {
const [clicked, setClicked] = useState(0);
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked % clickActions.length]();
}
}, [clicked, clickActions])
return <button onClick={() => setClicked(clicked => clicked + 1)}/>
}
In this case, clients of this component, need to be aware that they somehow need to prevent clickActions from being a different instance on every render. For example, it could simply be a constant, or be memoized by using useMemo.
Is there a best practice for making my clients aware of this? Are there ways to trigger compile time errors when this rule is violated?
NOTE: I realize this particular case can be solved without using useEffect, but it's just a simple example to illustrate the pattern. I'm not interested in solving this particular problem, but in how to solve the general problem.
There's nothing you can do by way of TypeScript trickery to enforce that clickActions can't change every time the parent renders.
However, you can simply remove clickActions from useEffect’s dependency list. You have to be careful, in general, when doing this, but in this case, it's safe, because the callback you’re passing to useEffect synchronously executes an action when clicked changes, which means the callback will have a reference to the most recent clickActions when it needs it.
This is analogous to the sample operator in RxJS; i.e. clickActions is sampled by clicked.
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked]();
}
}, [clicked])
Please also note that even when the parent uses useMemo, it's not guaranteed that it won’t recreate clickActions at a time of React’s choosing:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
(see useMemo docs)
It's safe to assume useCallback comes with a similar caveat, although it's not specifically mentioned in the docs.
generally if you have a function that is being passed around its important (especially if its 'created' within a function) to use useCallback when creating it. This ensures that the "props don't change" and thus preventing a rerender.
parentFunction = useCallback(()=>{
if (specialVar==='dance'){
return () => {
console.log('dance')
}
}
else {
return () => {
console.log('do the boogy')
}
}
}, [specialVar])

Why does axios.get a side effect and axios.post not one?

I'm following a react tutorial about todo-list with jsonplaceholder. In it we need to make a get request to https://jsonplaceholder.typicode.com/todos, and we have this code:
const [todos, setTodos] = useState([]);
useEffect(
() => {
Axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
.then(({data}) => {
setTodos(data);
});
}, []);
According to what I read, effects mean that function changes something outside of its scope, and not just return a value, and also with the same parameters it can give different results. At least that's how I understand about side-effects. But how does it apply in the context of this get? Does axios.get change anything, or does it return different value with different calls? I know that making a request to a third party is always an effect, but how does that work? At the same time I have addTodo function:
const addTodo = (title) => {
Axios.post("https://jsonplaceholder.typicode.com/todos", {
title: title,
completed: false
}).then(({data}) => setTodos([...todos, data]));
}
Why does this not need useEffect hook. It seems like addTodo changes the value of todos state, does it not? Why is there not useEffect() this time. Thanks for reading and my apology if there are a little bit too many questions. I don't think they need to be asked seperately.
This has nothing to do with axios and everything to do with when you want to execute your code. Just to clarify, axios doesn't need useEffect to work at all.
useEffect is a hook that allows you to perform actions after your component has mounted. It makes sense to place code in here that you perhaps only need to run once e.g. loading some data, hooking up event handlers, update the DOM etc. In your example, you load your list of Todos here, which makes sense as you probably only need to do this one time.
For code that doesn't need to run right away, like your addTodo, then you can trigger this as and when e.g. on a button click, timer, whatever makes sense for your application.
Note - useEffect can be triggered more than once using dependencies but it's not important for this example

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.

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