Home > other >  How can re-render a custom hook when its input value change from undefined to a valid value?
How can re-render a custom hook when its input value change from undefined to a valid value?

Time:08-14

I created a custom hook that get a ref to check the scroll state that is in top of its parent or in bottom of it or even it is scrollable or not. So I pass a ref object to my hook but at first that the ref is null cause that anything does not work in my hook but I wanna when my ref get the right value re-call my hook with this right value but it doesn't work and my custom hook call just one time and its ref value is null

below is my hook :

import { RefObject, useEffect, useState } from "react"

export const useScrollState = <TElement extends HTMLElement>(ref: RefObject<TElement | null>) => {
  const [isTop, setIsTop] = useState(true)
  const [isBottom, setIsBottom] = useState(false)
  const targetRef = ref?.current
  const isScrollable: boolean =
    (targetRef && targetRef.offsetHeight < targetRef.scrollHeight) ?? false



  const scrollHandler = () => {
    if (targetRef && Number(targetRef.scrollTop.toString()) === 0) {
      setIsTop(true)
    } else setIsTop(false)

    if (targetRef && targetRef.offsetHeight   targetRef.scrollTop >= targetRef.scrollHeight) {
      setIsBottom(true)
    } else {
      setIsBottom(false)
    }
  }

  useEffect(() => {


    targetRef?.addEventListener("scroll", scrollHandler)

    return () => {
      targetRef?.removeEventListener("scroll", scrollHandler)
    }
  }, [])

  return {
    isTop,
    isBottom,
    isScrollable,
  }
}

and there is my use case like below:

const divRef = useRef<HTMLDivElement>(null)
  const { isTop, isBottom, isScrollable } = useScrollState(divRef)

  return (
    <>

      <div ref={divRef}>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
        <p style={{ border: "dashed 1px red", height: "150px" }}>test</p>
      </div>
    </>
  )

CodePudding user response:

React refs are meant to remember some value that doesn't require a rerender when the value changes. So it is not possible to refresh your hook based on whether the ref value has changed. If you need your component to rerender, you have to use state.

React docs say:

When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a ref.

Source: https://beta.reactjs.org/learn/referencing-values-with-refs

UPDATE: As per my understanding, you have ref as null so when you try to attach an event listener, it doesn't work. To address this, you can use callback ref. Basically, you pass a useCallback() function as ref into the DOM node. This way, you will be notified when ref updates even if the DOM node is created later:

const divRef = useCallback(node => {
  // set ref in a state variable inside your custom hook
  // basically you need a new useState call in your custom hook
}, [])

<div ref={divRef}>
  ...
</div>

You can read more about it here: https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

CodePudding user response:

targetRef is available after the very first render, in this useEffect callback:

useEffect(() => {


targetRef?.addEventListener("scroll", scrollHandler)

return () => {
  targetRef?.removeEventListener("scroll", scrollHandler)
}
}, [])

But you don't need to pass isTop or isBottom as dependecies, instead pass an empty array for useEffect to only run after the first render.

  • Related