News for hackers. Stuff that matters.
Re:React: You Might Not Need an Effect by davedx on Thursday March 23, 2023 @05:37AM (#35272199)
No you're right, I completely agree with you. It's also very telling that there's also this very long article about useEffect dependencies: https://react.dev/learn/removing-effect-dependencies

useEffect and dependencies are the worst part of React at the moment.

The reason is, you do need to use effects, everywhere in your code, constantly. And dependencies, and the linter rule that goes with them, are nasty ergonomics, and hard to reason about.

The article I linked does explain it all quite well, and most devs I work with do understand the dependencies rules, but the rule still needs to be disabled often. Why?

Often the lint errors really are just wrong. Just like ChatGPT is often simply just wrong, no ifs or buts. For example: you have a callback function declared as an expression, that the useEffect hook calls. The linter will say "oh this is an expression, therefore it needs to be declared as a dependency". But it doesn't, because looking at the code as a human, you can immediately see the function never changes.

If you follow the linter, you will inevitably end up going down a destructive and unnecessary refactoring. You'll end up with unnecessary `useCallbacks` sprinkled everywhere that hurt readability for nothing. Often, you'll have to refactor the dependencies out of the component entirely, splitting one cohesive component into two, just to satisfy the linter out of an abundance of caution.

Junior devs will be confused. Senior devs will be frustrated. Every react codebase I've worked on has gagged this linter warning.

So how should this work?

It shouldn't be a a linter error in the first place. Linters should be used to enforce coding standards, not fix application bugs. I feel very strongly on this.

Re:React: You Might Not Need an Effect by zarzavat on Thursday March 23, 2023 @05:59AM (#35272339)
The funny thing is that React already kind of has its own programming language: JSX. If they wanted to, they could add language features to support React. I expect that would be very controversial though.
Re:React: You Might Not Need an Effect by yencabulator on Friday March 24, 2023 @06:59PM (#35296815)
Svelte is pretty pleasant, compared to React. It's fundamentally a JS-to-JS compiler that adds reactive assignments, and ridiculously much more usable than useEffectHopeThisWasTheRightThing.

    let count = 1;
    
    // this is reactive
    $: doubled = count * 2;
    
    function handleClick() {
      count += 1;
    }
Re:React: You Might Not Need an Effect by Rapzid on Thursday March 23, 2023 @06:26AM (#35272530)
There is a new experimental hook, useEffectEvent, whose only discernable functionality based on the documentation and information available is to quiet the linter. I'm not sure that's all it does since I haven't dug into the React source code, but that's what many are speculating.
Re:React: You Might Not Need an Effect by andrewingram on Thursday March 23, 2023 @06:37AM (#35272610)
It makes it so that the same instance of a function is used for all renders, but that it always points to the data closed over by the latest render; it eliminates a whole class of scenarios that normally lead to gnarly dependency array tomfoolery with useCallback, useMemo and useEffect.

It's quite a common hook to try to invent in user-land (i've seen it in numerous projects), it's not just a linter silencer.

Re:React: You Might Not Need an Effect by Rapzid on Thursday March 23, 2023 @06:52AM (#35272736)
> but that it always points to the data closed over by the latest render

It's my understanding that this was to be the behavior of the canceled useEvent hooks but that the new useEffectEvent hook does not return a stable reference.

Do you have sources for the behavior of the new hook?

Re:React: You Might Not Need an Effect by andrewingram on Thursday March 23, 2023 @07:11AM (#35272914)
https://react.dev/learn/separating-events-from-effects#decla...

> Here, onConnected is called an Effect Event. It’s a part of your Effect logic, but it behaves a lot more like an event handler. The logic inside it is not reactive, and it always “sees” the latest values of your props and state.

Re:React: You Might Not Need an Effect by Rapzid on Thursday March 23, 2023 @07:21AM (#35272990)
A bare closure would always see the latest values because it closes over them during render. And the latest useEffect closure will have closed over that method..

Here is the test suite for useEffectEvent and the test ensuring it does not provide a stable reference: https://github.com/facebook/react/blob/main/packages/react-r...

Re:React: You Might Not Need an Effect by andrewingram on Thursday March 23, 2023 @08:12AM (#35273483)
You're right, i'll redact/edit. Hm, this doc page is exceptionally confusing.
Re:React: You Might Not Need an Effect by robby_w_g on Thursday March 23, 2023 @01:57PM (#35278711)
As far as I can tell, useEffectEvent is just the useEvent hook but renamed to reduce scope. useEvent was originally going to provide performance optimizations when creating event handlers in a function component and it was going to solve the issue of event handlers causing effects to re-fire[1].

Now it's just going to focus on the effect issue.

[1] https://github.com/reactjs/rfcs/blob/useevent/text/0000-usee...

Re:React: You Might Not Need an Effect by cbovis on Thursday March 23, 2023 @07:19AM (#35272978)
"For example: you have a callback function declared as an expression, that the useEffect hook calls. The linter will say "oh this is an expression, therefore it needs to be declared as a dependency". But it doesn't, because looking at the code as a human, you can immediately see the function never changes."

Can you elaborate on this? In my experience this isn't the case. If a function is declared outside of the component scope then it won't trigger a lint error. If it's defined within the component scope and not wrapped in useCallback then it does in fact change on every render and the lint error is being raised for good reason.

Re:React: You Might Not Need an Effect by davedx on Thursday March 23, 2023 @08:07AM (#35273421)
> If it's defined within the component scope and not wrapped in useCallback then it does in fact change on every render and the lint error is being raised for good reason.

So I just did some playing around to investigate this further, and it seems to be more subtle than I thought.

If you have a function expression but the expression is not dependent on any outer scoped variables, the linter does not complain that you didn't specify the function itself as a dependency in useEffect. Example:

```

  let bar = "bar";

  const setResult = (result: string) => {
    // Won't trigger "exhaustive-deps"
    setBio(result);
    // Will trigger "exhaustive-deps"
    if (bar === "1") setBio(result);
  };

  useEffect(() => {
    setResult("");
  }, [person]);
```

So there's some quite sophisticated analysis of the AST going on in the lint rule.

I still think that this rule is too complicated for a linter. (Bugs like inter-dependencies in your data and code should be caught by unit tests).

But it's technically pretty impressive.

Edit: I did some more investigations.

If I make a function that depends on something very unpure (like checking window.location), the linter fires if the unpure function is in the outer scope of the hook, but not if it isn't. Even though the dependency is the same. So the linter rule can only identify locally scoped dependencies I think.

Re:React: You Might Not Need an Effect by joshribakoff on Thursday March 23, 2023 @08:18AM (#35273544)
> Bugs like inter-dependencies in your data and code should be caught by unit tests

Why write a ton of unit tests for what a static check can just do “for free?”. Not sure i follow your logic. I agree React can feel like a nuisance, but the lint rule itself is not the issue i think you have, it sounds like your issue is just with hooks.

Re:React: You Might Not Need an Effect by Jcampuzano2 on Thursday March 23, 2023 @08:47AM (#35273885)
Its not necessarily that the function depends on something 'very unpure', its if it depends on anything which is non-primitive that is created or assigned within the component fn body.

window.location returns a Location object which is not a primitive, and so it will trigger exhaustive deps if it is being used by way of a reference inside the component render function (assigned to a variable), since it could theoretically be a different object reference on subsequent renders.

Since the linter can't necessarily tell if the reference has changed out from under it on subsequent renders due to the fact that it could be mutably changed, it marks it as a necessary dep.

Re:React: You Might Not Need an Effect by kajaktum on Thursday March 23, 2023 @09:02AM (#35274061)
This seems obvious to me? Your lambda captures an outside object that will change with each render pass?
Re:React: You Might Not Need an Effect by cush on Thursday March 23, 2023 @10:11AM (#35275103)
I recommend you read the article again. Just because window.location changes outside scope doesn't mean that React would update the component when window.location changes just because you put it in a dependency array.

This is because props or state didn't change and the parent didn't rerender.

UseEffect dependencies aren't observables. You'd need to wire up an event listener on unload, hashChange, etc

https://stackoverflow.com/questions/58442168/why-useeffect-d...