DEV Community

Imamuzzaki Abu Salam
Imamuzzaki Abu Salam

Posted on • Originally published at blog.imam.dev

Hello React, Goodbye useEffect (I Hope)

In this article, I will show you how to use React to replace useEffect in most cases.

I've been watching "Goodbye, useEffect" by David Khoursid, and it's 🤯 blows my mind in a 😀 good way. I agree that useEffect has been used so much that it makes our code dirty and hard to maintain. I've been using useEffect for a long time, and I'm guilty of misusing it. I'm sure React has features that will make my code cleaner and easier to maintain.

What is useEffect?

useEffect is a hook that allows us to perform side effects in function components. It combines componentDidMount, componentDidUpdate, and componentWillUnmount in a single API. It's a compelling hook that will enable us to do many things. But it's also a very dangerous hook that can cause a lot of bugs.

Why useEffect is dangerous?

Let's take a look at the following example:

import React, { useEffect } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)
    return () => clearInterval(interval)
  }, [])

  return <div>{count}</div>
}
Enter fullscreen mode Exit fullscreen mode

It's a simple counter that increases every second. It uses useEffect to set an interval. It also uses useEffect to clear the interval when the component unmounts. The code snippet above is a widespread use case for useEffect. It's a straightforward example, but it's also a terrible example.

The problem with this example is that the interval is set every time the component re-renders. If the component re-renders for any reason, the interval will be set again. The interval will be called twice per second. It's not a problem with this simple example, but it can be a big problem when the interval is more complex. It can also cause memory leaks.

How to fix it?

There are many ways to fix this problem. One way is to use useRef to store the interval.

import React, { useEffect, useRef } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)
  const intervalRef = useRef()

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)
    return () => clearInterval(intervalRef.current)
  }, [])

  return <div>{count}</div>
}
Enter fullscreen mode Exit fullscreen mode

The above code is a lot better than the previous example. It doesn't set the interval every time the component re-renders. But it still needs improvement. It's still a bit complicated. And it still uses useEffect, which is a very dangerous hook.

useEffect is not for effects

As we know about useEffect, it combines componentDidMount, componentDidUpdate, and componentWillUnmount in a single API. Let's give some examples of it:

useEffect(() => {
  // componentDidMount?
}, [])
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
  // componentDidUpdate?
}, [something, anotherThing])
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
  return () => {
    // componentWillUnmount?
  }
}, [])
Enter fullscreen mode Exit fullscreen mode

It's effortless to understand. useEffect is used to perform side effects when the component mounts, updates, and unmounts. But it's not only used to perform side effects. It's also used to perform side effects when the component re-renders. It's not a good idea to perform side effects when the component re-renders. It can cause a lot of bugs. It's better to use other hooks to perform side effects when the component re-renders.

useEffect is not a lifecycle hook.


import React, { useState, useEffect } from 'react'

const Example = () => {
  const [value, setValue] = useState('')
  const [count, setCount] = useState(-1)

  useEffect(() => {
    setCount(count + 1)
  })

  const onChange = ({ target }) => setValue(target.value)

  return (
    <div>
      <input type="text" value={value} onChange={onChange} />
      <div>Number of changes: {count}</div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

useEffect is not a state setter

import React, { useState, useEffect } from 'react'

const Example = () => {
  const [count, setCount] = useState(0)

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`
  }) // <-- this is the problem, 😱 it's missing the dependency array

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

I recommend reading this documentation: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Imperative vs Declarative

Imperative: When something happens, execute this effect.

Declarative: When something happens, it will cause the state to change and depending (dependency array) on which parts of the state changed, this effect should be executed, but only if some condition is true. And React may execute it again for no reason concurrent rendering.

Concept vs Implementation

Concept:

useEffect(() => {
  doSomething()

  return () => cleanup()
}, [whenThisChanges])
Enter fullscreen mode Exit fullscreen mode

Implementation:

useEffect(() => {
  if (foo && bar && (baz || quo)) {
    doSomething()
  } else {
    doSomethingElse()
  }

  // oops, I forgot the cleanup
}, [foo, bar, baz, quo])
Enter fullscreen mode Exit fullscreen mode

Real-world implementation:

useEffect(() => {
  if (isOpen && component && containerElRef.current) {
    if (React.isValidElement(component)) {
      ionContext.addOverlay(overlayId, component, containerElRef.current!);
    } else {
      const element = createElement(component as React.ComponentClass, componentProps);
      ionContext.addOverlay(overlayId, element, containerElRef.current!);
    }
  }
}, [component, containerElRef.current, isOpen, componentProps]);
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
  if (removingValue && !hasValue && cssDisplayFlex) {
    setCssDisplayFlex(false)
  }
  setRemovingValue(false)
}, [removingValue, hasValue, cssDisplayFlex])
Enter fullscreen mode Exit fullscreen mode

It's scary to write this code. Furthermore, it will be normal in our codebase and messed up. 😱🤮

Where do effects go?

React 18 runs effects twice on the mount (in strict mode). Mount/effect (╯°□°)╯︵ ┻━┻ -> Unmount (simulated)/cleanup ┬─┬ /( º _ º /) -> Remount/effect (╯°□°)╯︵ ┻━┻

Should it be placed outside of the component? The default useEffect? Uh... awkward. Hmm... 🤔 We couldn't put it in render since it should be no side-effects there because render is just like the right-hand of a math equation. It should be only the result of the calculation.

What is useEffect for?

Synchronization

useEffect(() => {
  const sub = createThing(input).subscribe((value) => {
    // do something with value
  })

  return sub.unsubscribe
}, [input])
Enter fullscreen mode Exit fullscreen mode


useEffect(() => {
  const handler = (event) => {
    setPointer({ x: event.clientX, y: event.clientY })
  }

  elRef.current.addEventListener('pointermove', handler)

  return () => {
    elRef.current.removeEventListener('pointermove', handler)
  }
}, [])
Enter fullscreen mode Exit fullscreen mode

Action effects vs Activity effects

 Fire-and-forget            Synchronized
 (Action effects)        (Activity effects)

        0              ----------------------       ----------------- - - -
        o              o   |     A   |      o       o     | A   |   A
        o              o   |     |   |      o       o     | |   |   |
        o              o   |     |   |      o       o     | |   |   |
        o              o   |     |   |      o       o     | |   |   |
        o              o   |     |   |      o       o     | |   |   |
        o              o   |     |   |      o       o     | |   |   |
        o              o   V     |   V      o       o     V |   V   |
o-------------------------------------------------------------------------------->
                                       Unmount      Remount
Enter fullscreen mode Exit fullscreen mode

Where do action effects go?

Event handlers. Sorta.

<form
  onSubmit={(event) => {
    // 💥 side-effect!
    submitData(event)
  }}
>
  {/* ... */}
</form>
Enter fullscreen mode Exit fullscreen mode

There is excellent information in Beta React.js. I recommend reading it. Especially the "Can event handlers have side effects?" part.

Absolutely! Event handlers are the best place for side effects.

Another great resource I want to mention is Where you can cause side effects

In React, side effects usually belong inside event handlers.

If you've exhausted all other options and can't find the right event handler for your side effect, you can still attach it to your returned JSX with a useEffect call in your component. This tells React to execute it later, after rendering, when side effects are allowed. However, this approach should be your last resort.

"Effects happen outside of rendering" - David Khoursid.

(state) => UI
(state, event) => nextState // 🤔 Effects?
Enter fullscreen mode Exit fullscreen mode

UI is a function of the state. As all the current states are rendered, it will produce the current UI. Likewise, when an event happens, it will create a new state. And when the state changes, it will build a new UI. This paradigm is the core of React.

When do effects happen?

Middleware? 🕵️ Callbacks? 🤙 Sagas? 🧙‍♂️ Reactions? 🧪 Sinks? 🚰 Monads(?) 🧙‍♂️ Whenever? 🤷‍♂️

State transitions. Always.

(state, event) => nextState
          |
          V
(state, event) => (nextState, effect) // Here
Enter fullscreen mode Exit fullscreen mode

Rerender illustration image

Where do action effects go? Event handlers. State transitions.

Which happen to be executed at the same time as event handlers.

We Might Not Need an Effects

We could use useEffect because we don't know that there is already a built-in API from React that can solve this problem.

Here is an excellent resource to read about this topic: You Might Not Need an Effect

We don't need useEffect for transforming data.

useEffect ➡️ useMemo (even though we don't need useMemo in most cases)

const Cart = () => {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  useEffect(() => {
    setTotal(items.reduce((total, item) => total + item.price, 0))
  }, [items])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Read and think about it again carefully 🧐.

const Cart = () => {
  const [items, setItems] = useState([])
  const total = useMemo(() => {
    return items.reduce((total, item) => total + item.price, 0)
  }, [items])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Instead of using useEffect to calculate the total, we can use useMemo to memoize the total. Even if the variable is not an expensive calculation, we don't need to use useMemo to memoize it because we're basically trading performance for memory.

Whenever we see setState in useEffect, it's a warning sign that we can simplify it.

Effects with external stores? useSyncExternalStore

useEffect ➡️ useSyncExternalStore

❌ Wrong way:

const Store = () => {
  const [isConnected, setIsConnected] = useState(true)

  useEffect(() => {
    const sub = storeApi.subscribe(({ status }) => {
      setIsConnected(status === 'connected')
    })

    return () => {
      sub.unsubscribe()
    }
  }, [])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

✅ Best way:

const Store = () => {
  const isConnected = useSyncExternalStore(
    // 👇 subscribe
    storeApi.subscribe,
    // 👇 get snapshot
    () => storeApi.getStatus() === 'connected',
    // 👇 get server snapshot
    true
  )

  // ...
}
Enter fullscreen mode Exit fullscreen mode

We don't need useEffect for communicating with parents.

useEffect ➡️ eventHandler

❌ Wrong way:

const ChildProduct = ({ onOpen, onClose }) => {
  const [isOpen, setIsOpen] = useState(false)

  useEffect(() => {
    if (isOpen) {
      onOpen()
    } else {
      onClose()
    }
  }, [isOpen])

  return (
    <div>
      <button
        onClick={() => {
          setIsOpen(!isOpen)
        }}
      >
        Toggle quick view
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

📈 Better way:

const ChildProduct = ({ onOpen, onClose }) => {
  const [isOpen, setIsOpen] = useState(false)

const handleToggle = () => {
  const nextIsOpen = !isOpen;
  setIsOpen(nextIsOpen)

  if (nextIsOpen) {
    onOpen()
  } else {
    onClose()
  }
}

  return (
    <div>
      <button
        onClick={}
      >
        Toggle quick view
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

✅ Best way is to create a custom hook:

const useToggle({ onOpen, onClose }) => {
  const [isOpen, setIsOpen] = useState(false)

  const handleToggle = () => {
    const nextIsOpen = !isOpen
    setIsOpen(nextIsOpen)

    if (nextIsOpen) {
      onOpen()
    } else {
      onClose()
    }
  }

  return [isOpen, handleToggle]
}

const ChildProduct = ({ onOpen, onClose }) => {
  const [isOpen, handleToggle] = useToggle({ onOpen, onClose })

  return (
    <div>
      <button
        onClick={handleToggle}
      >
        Toggle quick view
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

We don't need useEft for initializing global singletons.

useEffect ➡️ justCallIt

❌ Wrong way:

const Store = () => {
  useEffect(() => {
    storeApi.authenticate() // 👈 This will run twice!
  }, [])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

🔨 Let's fix it:

const Store = () => {
  const didAuthenticateRef = useRef()

  useEffect(() => {
    if (didAuthenticateRef.current) return

    storeApi.authenticate()

    didAuthenticateRef.current = true
  }, [])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

➿ Another way:

let didAuthenticate = false

const Store = () => {
  useEffect(() => {
    if (didAuthenticate) return

    storeApi.authenticate()

    didAuthenticate = true
  }, [])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

🤔 How if:

storeApi.authenticate()

const Store = () => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

🍷 SSR, huh?

if (typeof window !== 'undefined') {
  storeApi.authenticate()
}
const Store = () => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

🧪 Testing?

const renderApp = () => {
  if (typeof window !== 'undefined') {
    storeApi.authenticate()
  }

  appRoot.render(<Store />)
}
Enter fullscreen mode Exit fullscreen mode

We don't necessarily need to place everything inside a component.

We don't need useEffect for fetching data.

useEffect ➡️ renderAsYouFetch (SSR) or useSWR (CSR)

❌ Wrong way:

const Store = () => {
  const [items, setItems] = useState([])

  useEffect(() => {
    let isCanceled = false

    getItems().then((data) => {
      if (isCanceled) return

      setItems(data)
    })

    return () => {
      isCanceled = true
    }
  })

  // ...
}
Enter fullscreen mode Exit fullscreen mode

💽 Remix way:

import { useLoaderData } from '@renix-run/react'
import { json } from '@remix-run/node'
import { getItems } from './storeApi'

export const loader = async () => {
  const items = await getItems()

  return json(items)
}

const Store = () => {
  const items = useLoaderData()

  // ...
}

export default Store
Enter fullscreen mode Exit fullscreen mode

⏭️🧹 Next.js (appDir) with async/await in Server Component way:

// app/page.tsx
async function getData() {
  const res = await fetch('https://api.example.com/...')
  // The return value is *not* serialized
  // You can return Date, Map, Set, etc.

  // Recommendation: handle errors
  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error('Failed to fetch data')
  }

  return res.json()
}

export default async function Page() {
  const data = await getData()

  return <main></main>
}
Enter fullscreen mode Exit fullscreen mode

⏭️💁 Next.js (appDir) with useSWR in Client Component way:

// app/page.tsx
import useSWR from 'swr'

export default function Page() {
  const { data, error } = useSWR('/api/data', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data}!</div>
}
Enter fullscreen mode Exit fullscreen mode

⏭️🧹 Next.js (pagesDir) in SSR way:

// pages/index.tsx
import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async () => {
  const res = await fetch('https://api.example.com/...')
  const data = await res.json()

  return {
    props: {
      data,
    },
  }
}

export default function Page({ data }) {
  return <div>hello {data}!</div>
}
Enter fullscreen mode Exit fullscreen mode

⏭️💁 Next.js (pagesDir) in CSR way:

// pages/index.tsx
import useSWR from 'swr'

export default function Page() {
  const { data, error } = useSWR('/api/data', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data}!</div>
}
Enter fullscreen mode Exit fullscreen mode

🍃 React Query (SSR way:

import { getItems } from './storeApi'
import { useQuery } from 'react-query'

const Store = () => {
  const queryClient = useQueryClient()

  return (
    <button
      onClick={() => {
        queryClient.prefetchQuery('items', getItems)
      }}
    >
      See items
    </button>
  )
}

const Items = () => {
  const { data, isLoading, isError } = useQuery('items', getItems)

  // ...
}
Enter fullscreen mode Exit fullscreen mode

⁉️ Really ⁉️ What should we use? useEffect? useQuery? useSWR?

or... just use() 🤔

use() is a new React function that accepts a promise conceptually similar to await. use() handles the promise returned by a function in a way that is compatible with components, hooks, and Suspense. Learn more about use() in the React RFC.

function Note({ id }) {
  // This fetches a note asynchronously, but to the component author, it looks
  // like a synchronous operation.
  const note = use(fetchNote(id))
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Fetching in useEffect problems

🏃‍♂️ Race conditions

🔙 No instant back button

🔍 No SSR or initial HTML content

🌊 Chasing waterfall

  • Reddit, Dan Abramov

Conclusion

From fetching data to fighting with imperative APIs, side effects are one of the most significant sources of frustration in web app development. And let's be honest, putting everything in useEffect hooks only helps a little. Thankfully, there is a science (well, math) to side effects, formalized in state machines and statecharts, that can help us visually model and understand how to orchestrate effects, no matter how complex they get declaratively.

Resources

Latest comments (51)

Collapse
 
fr0stf0x profile image
fr0stf0x

PLEASE READ DOCUMENTATION OF TECHNOLOGIES YOU USE BEFORE YOU START COMPLAINING ABOUT IT...

please delete this post. you are misleading other people especially juniors!

Collapse
 
eecolor profile image
EECOLOR • Edited

The first example and description do not seem to match. Either remove the dependency array of the first example or clearly state that it could be easily forgotten and thus create problems.

At the second example you say "The above code is a lot better than the previous example", it however is not. It is a worse actually.

Maybe I misunderstand, but when useEffect has a dependency array that is empty it will not trigger on each render.

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

You're correct, I was typo when copy-pasting from the source code. It suppousedly not written like that.

Still the point is, don't use useEffect for everyhting.

Collapse
 
insidewhy profile image
insidewhy • Edited

You're misleading people with your opening example. The effect does not run on every render when you use an empty dependency array. Effects only run when a dependency changes, an empty array means they don't run until the component is mounted again (not rendered again). What would the point be of specifying the depdendency array if the effect ran on every render?

So I feel kinda bad that you wrote a lot more to this article when you couldn't get the basics right. I didn't read it because it wouldn't be worthwhile reading the ideas of someone who doesn't understand the concepts, but can you please just delete this or something so you don't mislead a lot of people?

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

I'm sorry for that example, It was my fault copying it from false clipboard history. I think I should fix it before any junior mislead by this example.

Thank you for pointing out.

Collapse
 
insidewhy profile image
insidewhy

You say it's a mistake with copy and pasting but your next code sample follows on from it.

Then your next code sample also does things with useRef that do nothing but make the code more complicated, more verbose and less performant.

Could you please delete this post before other developers who might not have so much knowledge of understanding get misled by it. Having incorrect information about programming online has the potential to be really harmful to others and yourself.

Collapse
 
harmonygrams profile image
Harmony

Ikr
As a newbie, it's kinda hard to distinguish between valid info and the opposite especially when the OP has a lot of followers.

Collapse
 
fr0stf0x profile image
fr0stf0x

@harmonygrams this article is trash. dont read it

Collapse
 
sergeysova profile image
Sergey Sova

The best way is to move all the business logic out from the components and the React code. A good candidate to use is — effector. It can handle all business logic cases without ruining logic with View-framework implementation details

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

yep, don't just mix everything in a single place if there is no purpose behind it. It'll be better if we split and move every part of our code with purpose and objective.

Collapse
 
ave profile image
Alexander

Clearly, author has no good understanding of useEffect and yet has written a massive article about its dangers.

The code snippet above is a widespread use case for useEffect. It's a straightforward example, but it's also a terrible example.
The problem with this example is that the interval is set every time the component re-renders.

The empty dependency array will make effect run only once when component mounts. It is in the docs.
The second example that claims to fix the issue does not actually do anything useful at all. There is absolutely no need to store the interval in the reference as it is perfectly fine inside the closure in the example #1.

This is so exemplary of what the modern internet knowledge have become: anyone posts whatever they believe and a fair bunch of others will consume it as expert knowledge (look at 71 likes!) multiplying amounts of nonsense which is already plentiful in the internet.

Collapse
 
insidewhy profile image
insidewhy

I know, and in spite of all the comments pointing out what a harmful and mistaken article it is, they won't delete it.

Collapse
 
itslately profile image
Lately • Edited

This is the new hot take era of "software development". YouTube promising "learn React in 5 hours", social media, and sites like dev.to here, contribute to people thinking they should be teaching others when they don't really have an understanding of the tools they're using. I love that more people are interested in programming, but too many don't grasp where they are in their journey and then repeat the same bad advice they read.

Collapse
 
vbilopav profile image
vbilopav

Meanwhile in Svelte

<script>
let c = 0;
setTimeout (() => c++, 1000);
</script>
<div>{c}</div>

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

🤣 good comparison

Collapse
 
shinkei profile image
Jorge Ramírez

thanks, now I think to my self, why is useEffect sold as a replacement to the react lifecycles if it is causing so much trouble and confusion?.

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

Because it is the only solution right now, and XState gave us another solution also.

We should pay a lot attention to prevent useEffect from causing a bug or make the code messy.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

Consider doing some positive things (e.g. contributing to main post, open a discussion, etc.) or please be quite.

Your comment not helping nor useful to anyone who didn't understand ur point of view.

Thread Thread
 
insidewhy profile image
insidewhy

It's not a point of view, it's objective reality. The existence of this article is only going to confuse and mislead people, especially juniours, pointing that out is helpful.

Collapse
 
milhamh95 profile image
Muhammad Ilham hidayat • Edited

Hello React, Goodbye useEffect (I Hope)

So what are the differences between React and useEffect? . From what I know useEffect is one of the feature from React.

Is there a new framework called useEffect?

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
milhamh95 profile image
Info Comment hidden by post author - thread only accessible via permalink
Muhammad Ilham hidayat

saying sorry for laughing but you put a laughing emoji is the same as you laughing at me 👎

don't mock someone if they don't know at something. In the title you compare React library with useEffect feature in React. The title is confusing.

Collapse
 
iway1 profile image
iway1

useEffect is a really useful tool and solves a lot of common problems in react... Yeah there are plenty of antipatterns that newbies often create when using it, but that's not the tools fault.

The idea that useEffect is bad in anyway is misguided, it makes certain things that were really annoying to do in the past easy.

Side note - If your state is so complex in React that you need a state chart to understand it, then you're probably doing something wrong. Application state doesn't need to be complex for most apps. Keeping your state simple should always be the first step

Collapse
 
devdufutur profile image
Rudy Nappée • Edited
function Note({ id }) {
  // This fetches a note asynchronously, but to the component author, it looks
  // like a synchronous operation.
  const note = use(fetchNote(id))
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
    </div>
  )
}

Not sure it's a great Idea to fetch data on every render 😕

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

Consider cheching out React RFC, it's great API tho. just use()

Collapse
 
devdufutur profile image
Rudy Nappée

It may be a great API but you shouldn't trigger a http request on every render

Thread Thread
 
devdufutur profile image
Rudy Nappée

Unless there is caching involved in fetchNote you create a new promise on each render which shouldn't be what you intend to.

Collapse
 
leob profile image
leob • Edited

I don't know, I have the feeling this is too much ... this one really does it for me:

"useEffect is not for effects"

Seriously? I think you lost me there ... and we've been told a myriad times in blog posts and whatnot that we can/should use useEffect to fetch our data using axios or fetch or whatever, and now all of a sudden that's bad advice?

Collapse
 
josevs profile image
Jose V Sebastian • Edited

The problems you described in the article are problems because you don't really understand how useEffect works.
In React 18, according to documentation, they're focusing on concurrency. Meaning they're working on being able to run code in parallel in react. To that effort, they had implemented a lot of changes. One of which is to make components work with fast mounting and remount. And to achieve that the developers need to write proper code to handle that, which is forced by the mount-unmount-remount cycle in initial mounting of any react component, in react strict mode but ONLY in development server. This is what causes the useEffect to run twice, even with zero dependencies.
The set interval example you gave, you said interval is set on every re-render. That is definitely NOT the case because with zero dependencies, it's a violation of react component lifecycle to call useEffects on every render if zero dependency array is given. I think you are confusing re-render with re-mounting.

As for the authenticate example. I am obligated to say, what you're suggesting is HUGE BLUNDER!!! As I said before, react strict in development forces developers to handle asynchronous actions in useEffects properly. And for that reason, using ref to prevent subsequent authentication api calls is a bad idea. Cleanup function is a must for the above example. I'm assuming, that snippet of code belongs to an Authenticate component. To deal with strict mode assertion, you need to make sure any asynchronous calls happens AFTER react finish rendering. So you need to use eventloop to call api (use setTimeout), in cleanup function you need to cancel the call ( clear timeout); doing that, you'll make sure the synchronous mount-unmount won't cause the api call to initiate, and the next mount will send the call (you have to also deal user triggered un mounting separately, what I'm saying is only for strict mode assertion).
This will clear up the development requirement as prescribed by the team behind react ( it may change in the future). Now your application won't have unexpected behaviour in neither strict mode dev or production server. And your component will work fine for concurrency.

Also, one last thing, if your application has a bug or memory leak because you didn't cleanup useEffect, it's NOT a problem with useEffect; it's a problem with YOU! You're obligated, as a developer, to clean up after yourself (at least until a better alternative comes into mainstream). (C++ pointers had that requirement, until garbage collection became the standard).

PLEASE READ DOCUMENTATION OF TECHNOLOGIES YOU USE BEFORE YOU START COMPLAINING ABOUT IT...

Collapse
 
myfailemtions profile image
Martirossyan Ararat • Edited

just reading your comment is complicated

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam • Edited

I like your point 👍

Consider contribute the original post if you find something to improve. I knew it before I write this article, this blog is not a holy qur'an, something wrong is normal but the point still fine delivered. The objective of this article is to show the reader that useEffect is not the ultimate solution for everything, it will made the code dirty if we wrote a bunch of useEffect without doing anything about it.

Collapse
 
insidewhy profile image
insidewhy

If the whole point stems from your own misunderstanding then there isn't a point that stands.

Collapse
 
blnkspace profile image
AVI • Edited

I have been doing react since 2015, only UI lib I’ve used at any job. I am in no way a React hater, but I do see its shortcomings. You are essentially saying “the library has terrible design decisions as defaults, but it’s your fault for expecting it to not have taken those terrible choices, so you are stupid”. really reactionary and defensive.There are barely any other frameworks where re-running is opt out, and where it’s so common for a lot of PRs to get blocked due to infinite rerender in tests. the issue is not that everyone including leads and seniors are stupid, but that React lets this happen very easily. if your framework requires memoisation and refs by default for performance and not crashing, and you think that garbage collection is a good analogy, then maybe you should try other frameworks that have existed for years. React is the analog to “language without GC” and pretty much all the others have advanced.

Collapse
 
leob profile image
leob • Edited

Good heavens ... I have the feeling that as a React dev we're eternally busy solving low-level problems (shortcomings of React?), rather than business problems - reading this article enforces that feeling.

The "use" (pun intended) case for looking at other frameworks (sorry, "libraries"?) like Vue, Svelte, SolidJS gets stronger by the day.

Collapse
 
rxliuli profile image
rxliuli • Edited

Yes, react hooks are the worst among the many front-end framework hooks. vue/svelte/solidjs do not have these strange hooks rules.


PS: I know one could argue that this is a compromise made by react for x functionality, but I don't care

Collapse
 
bcostaaa01 profile image
Bruno

Meanwhile you have mutations and actions, computed and mounted, and so on, in Vue/Vuex 🙃

Thread Thread
 
leob profile image
leob

Yes, and? They're simple tools that solve known problems in a predictable way - I've never seen lengthy discussions in the Vue community about all of their pitfalls, and how my Vue components are re-rendering in mysterious was, or the necessity to wrap "memo" around stuff as an ugly workaround ... :)

Thread Thread
 
bcostaaa01 profile image
Bruno

You are right with your point in that! It would be interesting to open a discussion on these topics indeed! :)

But to avoid the component to re-render, you do not have to always wrap it in memo. You can use other approaches:

  • v-once -> strictly avoids re-renders, but has the drawback of not re-rendering even if the data changes.
  • watch -> when a reactive data property changes, it will re-render.
  • computed -> by defining a function which is based on reactive data properties, when these change, the function is re-evaluated and the component will re-render.

But this doesn't stop Vue re-rendering components from being weird, I agree with you...😅

Collapse
 
leob profile image
leob • Edited

React puts the burden on the developer to take care of "low level" stuff which the framework ("library") should take care of ... but well, we're using React because there's a ton of jobs and because it puts bread on the table, right? Not because we love it so much :)

Thread Thread
 
chaseholdren_68 profile image
Chase Holdren

i love it. hooks do need work tho

Thread Thread
 
leob profile image
leob

I'm starting to like React better as I'm getting the hang of it - but then again, when seeing an article like this with "useEffect is not for effects", followed by a ton of different ways we "should" do things instead, yeah well it just gives me the creeps ...

How come we never have these kinds of discussions when it's about other frameworks like Vue and so on? The answer is, because frameworks like Vue and the like are more batteries included, and built on more sound principles - a lot of what I see in React feels like a kludge.

Thread Thread
 
dannyengelman profile image
Danny Engelman

because it puts bread on the table, right? Not because we love it so much :)

If that is the argument for React, you should be doing COBOL; pays more than React nowadays

Thread Thread
 
leob profile image
leob

LOL you could be right - so where are the COBOL articles here on dev.to?

Thread Thread
 
dannyengelman profile image
Danny Engelman

COBOL devs stopped using Internet technologies after Gopher; WWW was just a fad.

To be serious; what if React is the new COBOL?

Thread Thread
 
leob profile image
leob

Right - yes, COBOL devs just stick to their tried-and-true 3 meters of printed IBM manuals ... :)

React the new COBOL? I think Java is already the new COBOL, maybe React can then become the new Java?

Collapse
 
josephabbey profile image
JosephAbbey

In the remix example for fetching, on the first line, you spelt remix wrong.

import { useLoaderData } from '@renix-run/react'

Collapse
 
imamdev_ profile image
Imamuzzaki Abu Salam

good point, you can fix it by making a PR tho. But, for now, it's ok for things be like that, because IDE will tell us if something typo.

Collapse
 
brense profile image
Rense Bakker

If you pass a dependency array to useEffect it should not execute on every rerender, only when the dependencies change will it execute again. It's worth noting that useQuery uses useEffect under the hood as well.

Collapse
 
henrywoody profile image
Henry Woody

You might think this invalidates the opening example, but it sort of helps make the point that many people do not understand how useEffect works and are therefore better off using a library that wraps useEffect for you.

Collapse
 
brense profile image
Rense Bakker

Well i think in the end it would be best if react devs learn how to properly use the useEffect hook... I think the reason people think its complicated is because they try to link it to old class based lifecycle hooks... Granted the React team itself contributed to that confusion in an effort to give people a migration strategy from react class based to hooks.

Some comments have been hidden by the post's author - find out more