প্রকাশিত

সালাম রিঅ্যাক্ট, সালাম ইউজইফেক্ট (ইনশাআল্লাহ)

লেখকগণ

এই আর্টিকেলে, আমি আপনাকে দেখাবো কিভাবে বেশিরভাগ ক্ষেত্রে useEffect ব্যবহারের পরিবর্তে React ব্যবহার করবেন।

আমি "Goodbye, useEffect" by David Khoursid ভিডিও দেখছিলাম, এবং এটা 🤯 আমার মন 🤯 😀 ভালভাবে 😀 উড়িয়ে দিয়েছে। আমি সম্মত যে useEffect এত বেশি ব্যবহার করা হয় যে এটি আমাদের কোডকে নোংরা এবং রক্ষণাবেক্ষণ করা কঠিন করে তোলে। আমি দীর্ঘদিন ধরে useEffect ব্যবহার করছি, এবং আমি এটির অনুচিত ব্যবহারের জন্য দোষী। আমি নিশ্চিত React-এর এমন ফিচার আছে যা আমার কোড কে আরও পরিষ্কার এবং রক্ষণাবেক্ষণ সহজ করে তুলবে।

useEffect কী?

useEffect একটি হুক যা আমাদের ফাংশন কম্পোনেন্টে সাইড ইফেক্ট করতে দেয়। এটি componentDidMount, componentDidUpdate এবং componentWillUnmount কে একক API তে সংযুক্ত করে। এটি একটি আকর্ষণীয় হুক যা আমাদের অনেক কাজ করতে সক্ষম করবে। তবে এটি একটি অত্যন্ত বিপজ্জনক হুক যা অনেক বাগ তৈরি করতে পারে।

কেন useEffect বিপজ্জনক?

চলুন নীচের উদাহরণটি দেখি:

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>
}

এটি একটি সহজ কাউন্টার যা প্রতি সেকেন্ডে বৃদ্ধি পায়। এটি useEffect ব্যবহার করে একটি ইন্টারভাল সেট করে। এটি useEffect ব্যবহার করে কম্পোনেন্ট অনমাউন্ট হলে ইন্টারভাল ক্লিয়ার করে। উপরের কোড স্নিপেট useEffect জন্য একটি ব্যাপক ব্যবহার ক্ষেত্র। এটি একটি সরল উদাহরণ তবে এটি একটি ভয়াবহ উদাহরণ ও বলতে হয়।

এই উদাহরণের সমস্যা হলো প্রতিবার কম্পোনেন্ট রি-রেন্ডার হলে ইন্টারভাল সেট হয়। যদি কোনও কারণে কম্পোনেন্ট রি-রেন্ডার হয়, তাহলে ইন্টারভাল আবার সেট হবে। ইন্টারভাল প্রতি সেকেন্ডে দু'বার কল হবে। এটি এই সরল উদাহরণের জন্য কোনও সমস্যা নয় তবে ইন্টারভাল যখন আরও জটিল হয় তখন এটি একটি বড় সমস্যা হতে পারে। এটি মেমোরি লি কও তৈরি করতে পারে।

কিভাবে ঠিক করবেন?

এই সমস্যা ঠিক করার অনেক উপায় আছে। একটি উপায় হলো ইন্টারভাল সংরক্ষণ করার জন্য useRef ব্যবহার করা।

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>
}

উপরের কোড পূর্ববর্তী উদাহরণ থেকে অনেক ভালো। এটি প্রতিবার কম্পোনেন্ট রি-রেন্ডার হলে ইন্টারভাল সেট করে না। তবে এটির আরও উন্নতির প্রয়োজন। এটি এখনও কিছুটা জটিল। এবং এটি এখনও useEffect ব্যবহার করে, যা একটি অত্যন্ত বিপজ্জনক হুক।

useEffect ইফেক্টের জন্য নয়

আমরা জানি যে useEffect, componentDidMount, componentDidUpdate এবং componentWillUnmount কে একক API তে সংযুক্ত করে। চলুন এটির কিছু উদাহরণ দিই:

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

বুঝতে সহজ। useEffect কম্পোনেন্ট মাউন্ট হলে, আপডেট হলে এবং অনমাউন্ট হলে সাইড ইফেক্ট করতে ব্যবহার করা হয়। তবে এটি কেবল সাইড ইফেক্ট করতে ব্যবহার করা হয় না। কম্পোনেন্ট রি-রেন্ডার হলে ও এটি সাইড ইফেক্ট করতে ব্যবহার করা হয়। কম্পোনেন্ট রি-রেন্ডার হলে সাইড ইফেক্ট করার জন্য এটি একটি ভালো ধারণা নয়। এটি অনেক বাগ তৈরি করতে পারে। কম্পোনেন্ট রি-রেন্ডার হলে সাইড ইফেক্ট করার জন্য অন্যান্য হুক ব্যবহার করা ভালো।

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>
  )
}

useEffect একটি স্টেট সেটার নয়

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>
  )
}

আমি এই ডকুমেন্টেশনটি পড়ার পরামর্শ দিচ্ছি: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

ইম্পেরেটিভ বনাম ডিক্লেয়ারেটিভ

ইম্পেরেটিভ: যখন কিছু ঘটে, এই ইফেক্ট চালু করুন।

ডিক্লেয়ারেটিভ: যখন কিছু ঘটে, এটি স্টেট কে পরিবর্তন করবে এবং (ডিপেন্ডেন্সি অ্যারে) স্টেটের কোন অংশে পরিবর্তন হয়েছে নির্ভর করে, এই ইফেক্ট চালু হওয়া উচিত, তবে কেবল যদি কোনও শর্ত সত্য হয়। এবং React কারণহীনভাবে সমবর্তী রেন্ডারিং জন্য এটি আবার চালু করতে পারে।

ধারণা বনাম কার্যকরকরণ

ধারণা:

useEffect(() => {
  doSomething()

  return () => cleanup()
}, [whenThisChanges])

কার্যকরকরণ:

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

  // oops, I forgot the cleanup
}, [foo, bar, baz, quo])

বাস্তব বিশ্বের কার্যকরকরণ:

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]);
useEffect(() => {
  if (removingValue && !hasValue && cssDisplayFlex) {
    setCssDisplayFlex(false)
  }
  setRemovingValue(false)
}, [removingValue, hasValue, cssDisplayFlex])

এই কোড লেখা ভয়ঙ্কর। এছাড়াও এটি আমাদের কোডবেস এ স্বাভাবিক হবে এবং বিশৃঙ্খলা স্থাপন করবে। 😱🤮

ইফেক্ট কোথায় যাবে?

React 18 মাউন্ট করার সময় (স্ট্রিক্ট মোডে) দু'বার ইফেক্ট চালায়। মাউন্ট/ইফেক্ট (╯°□°)╯︵ ┻━┻ -> অনমাউন্ট (সিমুলেটেড)/ক্লিনআপ ┬─┬ /( º _ º /) -> রিমাউন্ট/ইফেক্ট (╯°□°)╯︵ ┻━┻

এটি কম্পোনেন্টের বাইরে রাখা উচিত কি? ডিফল্ট useEffect? হুম... অস্বস্তিকর। হুম... 🤔 আমরা এটি রেন্ডারে রাখতে পারবো না কারণ এটির কোন সাইড-ইফেক্ট থাকা উচিত না কারণ রেন্ডার কেবল একটি গণিত সমীকরণের ডান হাত । এটি কেবল গণনার ফলাফল হওয়া উচিত।

useEffect কিসের জন্য?

সিঙ্ক্রোনাইজেশন

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

  return sub.unsubscribe
}, [input])
useEffect(() => {
  const handler = (event) => {
    setPointer({ x: event.clientX, y: event.clientY })
  }

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

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

অ্যাকশন ইফেক্ট বনাম অ্যাক্টিভিটি ইফেক্ট

 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

অ্যাকশন ইফেক্ট কোথায় যাবে?

ইভেন্ট হ্যান্ডলার। কিছুটা।

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

বেটা React.js তে অসাধারণ তথ্য আছে। আমি এটি পড়ার পরামর্শ দিচ্ছি। বিশেষ করে "Can event handlers have side effects?" part

অবশ্যই! ইভেন্ট হ্যান্ডলার সাইড ইফেক্টের জন্য সবচেয়ে ভালো স্থান।

আরেকটি অসাধারণ সংস্থান যা আমি উল্লেখ করতে চাই হলো Where you can cause side effects

React তে, সাইড ইফেক্ট সাধারণত ইভেন্ট হ্যান্ডলারের ভিতরে থাকে।

যদি আপনি অন্যান্য সব বিকল্প ব্যবহার করে ফেলেছেন এবং আপনার সাইড ইফেক্টের জন্য ঠিক ইভেন্ট হ্যান্ডলার খুঁজে পান না, তাহলে আপনি এখনও এটি আপনার ফেরত দেওয়া JSX তে আপনার কম্পোনেন্টে একটি useEffect কল দিয়ে সংযুক্ত করতে পারেন। এটি React কে রেন্ডারিং পরবর্তী, সাইড ইফেক্ট করার জন্য অনুমতি প্রাপ্ত হলে এটি চালু করতে বলে। তবে, এই প্রক্রিয়াটি আপনার শেষ অবলম্বন হওয়া উচিত।

"ইফেক্ট রেন্ডারিং বাইরে ঘটে" - ডেভিড খোরশিদ।

(state) => UI
(state, event) => nextState // 🤔 Effects?

UI স্টেটের একটি ফাংশন। যেহেতু বর্তমান সব স্টেট রেন্ডার করা হয়েছে, এটি বর্তমান UI উৎপাদন করবে। একইভাবে, যখন একটি ইভেন্ট ঘটে, এটি একটি নতুন স্টেট তৈরি করবে। এবং যখন স্টেট পরিবর্তন হয়, এটি একটি নতুন UI তৈরি করবে। এই প্যারাডাইম React -এর কোর।

ইফেক্ট কখন ঘটে?

মিডলওয়্যার? 🕵️ কলব্যাক? 🤙 সাগা? 🧙‍♂️ প্রতিক্রিয়া? 🧪 সিঙ্ক? 🚰 মোনাদ (?) 🧙‍♂️ যখনই? 🤷‍♂️

স্টেট সংক্রমণ। সর্বদা।

(state, event) => nextState
          |
          V
(state, event) => (nextState, effect) // Here

![Rerender illustration image](https://media.slid.es/uploads/174419/images/9663683/CleanShot_2022-06-22_at_20.24.08_2x.png align="left")

অ্যাকশন ইফেক্ট কোথায় যাবে? ইভেন্ট হ্যান্ডলার। স্টেট সংক্রমণ।

যা ইভেন্ট হ্যান্ডলারের সাথে একই সময়ে চালিত হয়।

আমাদের ইফেক্টের প্রয়োজন হতে পারে না

আমরা useEffect ব্যবহার করতে পারি কারণ আমরা জানি না যে React থেকে ইতিমধ্যে একটি নির্মিত API আছে যা এই সমস্যা সমাধান করতে পারে।

এই বিষয়ে পড়ার জন্য একটি অসাধারণ সংস্থান এখানে আছে: You Might Not Need an Effect

ডেটা পরিবর্তন করার জন্য আমাদের useEffect -এর প্রয়োজন হবে না।

useEffect ➡️ useMemo (যদিও বেশিরভাগ ক্ষেত্রে আমাদের useMemo -এর প্রয়োজন হবে না)

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

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

  // ...
}

এটি আবার যত্ন সহকারে পড়ুন এবং ভাবুন 🧐.

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

  // ...
}

useEffect ব্যবহার করে টোটাল গণনা করার পরিবর্তে, আমরা useMemo ব্যবহার করে টোটাল মেমোরাইজ করতে পারি। যদিও ভ্যারিয়েবলটি একটি মূল্যবান গণনা নয়, আমরা এটি মেমোরাইজ করার জন্য useMemo ব্যবহার করতে পারি না কারণ আমরা মূলত মেমোরির জন্য পারফর্মেন্স ব্যবসা করছি।

যখনই আমরা useEffect -এ setState দেখি, এটি একটি সতর্কতা সংকেত যে আমরা এটি সরলীকরণ করতে পারি।

বাহ্যিক স্টোর সহ ইফেক্ট? useSyncExternalStore

useEffect ➡️ useSyncExternalStore

❌ ভুল উপায়:

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

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

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

  // ...
}

✅ সর্বোত্তম উপায়:

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

  // ...
}

পিতামাতার সাথে যোগাযোগ করার জন্য আমাদের useEffect -এর প্রয়োজন হবে না।

useEffect ➡️ eventHandler

❌ ভুল উপায়:

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>
  )
}

📈 ভালো উপায়:

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>
  )
}

✅ সর্বোত্তম উপায় হলো একটি কাস্টম হুক তৈরি করা:

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>
  )
}

গ্লোবাল সিঙ্গলটন ইনিসিয়ালাইজ করার জন্য আমাদের useEffect -এর প্রয়োজন হবে না।

useEffect ➡️ justCallIt

❌ ভুল উপায়:

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

  // ...
}

🔨 চলুন এটি ঠিক করি:

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

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

    storeApi.authenticate()

    didAuthenticateRef.current = true
  }, [])

  // ...
}

➿ আরেকটি উপায়:

let didAuthenticate = false

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

    storeApi.authenticate()

    didAuthenticate = true
  }, [])

  // ...
}

🤔 যদি হয়:

storeApi.authenticate()

const Store = () => {
  // ...
}

🍷 SSR, হ্যাঁ?

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

🧪 পরীক্ষা?

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

  appRoot.render(<Store />)
}

আমাদের অবশ্যই কম্পোনেন্টের ভিতরে সবকিছু রাখার প্রয়োজন হবে না।

ডেটা ফেচ করার জন্য আমাদের useEffect -এর প্রয়োজন হবে না।

useEffect ➡️ renderAsYouFetch (SSR) অথবা useSWR (CSR)

❌ ভুল উপায়:

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

  useEffect(() => {
    let isCanceled = false

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

      setItems(data)
    })

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

  // ...
}

💽 Remix উপায়:

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

⏭️🧹 Next.js (appDir) async/await সহ Server Component উপায়:

// 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>
}

⏭️💁 Next.js (appDir) useSWR সহ Client Component উপায়:

// 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>
}

⏭️🧹 Next.js (pagesDir) SSR উপায়:

// 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>
}

⏭️💁 Next.js (pagesDir) CSR উপায়:

// 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>
}

🍃 React Query (SSR উপায়:

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)

  // ...
}

⁉️ সত্যিই ⁉️ আমাদের কী ব্যবহার করা উচিত? useEffect? useQuery? useSWR?

অথবা... কেবল use() 🤔

use() একটি নতুন React ফাংশন যা await -এর সাথে মিলে একটি প্রমিস গ্রহণ করে। use() কম্পোনেন্ট, হুক এবং Suspense -এর সাথে সামঞ্জস্যপূর্ণ একটি উপায়ে একটি ফাংশন দ্বারা ফেরত প্রমিস হ্যান্ডেল করে। React RFC -এ use() -এর বিষয়ে আরও জানুন।

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>
  )
}

useEffect -এ ফেচ করার সমস্যা

🏃‍♂️ রেস কন্ডিশন

🔙 অনস্থানীয় ব্যাক বোতাম

🔍 কোনও SSR অথবা প্রাথমিক HTML সামগ্রী নেই

🌊 জলপ্রপাত পিছনে পিছনে দৌড়ানো

  • Reddit, ড্যান অ্যাব্রামভ

উপসংহার

ডেটা ফেচ করার থেকে ইম্পেরেটিভ API -এর সাথে যুদ্ধ করার জন্য, সাইড ইফেক্ট ওয়েব অ্যাপ ডেভেলপমেন্টে হতাশার সবচেয়ে বড় উৎস গুলির একটি। এবং ঈমানদার হওয়া যাক, সবকিছু useEffect হুকে রাখা কেবল কিছুটা সাহায্য করে। ধন্যবাদ, সাইড ইফ