0

I'm trying to change the left style attribute of the element when a change happens but my code does not work, do you know why?

The Function Component:

const Link: React.FunctionComponent<{ name: string, href?: string }> = function ({ name, href }) {
          const filteredName = name.toLowerCase().trim().split(" ").join("")
          var res = 0
          useEffect(()=>{
               function handleResize() {

                    const w = globalThis.outerWidth
                    const h = globalThis.outerHeight
                    let index = 0
                    for (let elem = 0;elem<allDataElems.length;elem++) {
                         if (allDataElems[elem] === name) {
                              index = elem + 1
                              break
                         }
                    }
                    var elSize = null
                    try {
                         elSize = ulEl.current!.scrollTop + (ulEl.current!.firstElementChild!.scrollHeight * (index)) + index * 32 - 250
                    } catch {
                         elSize = 0
                    }
                    return (w*28*3/1000)*(elSize/h)
               }
               res = handleResize()
          })
          return <li style={{top: "20px",left: res + "px"}}><Anchor href={href || `/${name.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(filteredName as keyof typeof datas)}>{name}</a></Anchor></li>
     }

Where I used it:

    <Link name="Home" href="/"></Link>
    <Link name="Project"></Link>
    <Link name="Team"></Link>
    <Link name="Description"></Link>
    <Link name="Human Practices"></Link>
    <Link name="Judging Form"></Link>
    <Link name="Gallery"></Link>
Efe FRK
  • 177
  • 1
  • 13

3 Answers3

0

You shouldn't be using useRef inside a different context than the function component. You can read all the rules in the official documentation.

In your case, you can solve it by creating a single ref with an Object and storing the element references as key/value pairs like so:

const Navbar = function () {
  const linkRefs = useRef<{[key: string]: HTMLLIElement}>({});

  useEffect(() => {
    Object.values(linkRefs).forEach(element => {
      // use element to access each element
    });
  });

  return (
    <div>
      <Link ref={linkRefs.home} name="Home" href="/"></Link>
      <Link ref={linkRefs.projects} name="Projects" href="/projects"></Link>
      ...
    </div>
  );
};
Chris
  • 6,331
  • 1
  • 21
  • 25
  • I don't think it is because of the declaration of the refs. The problem is that in useEffect always the initial value of the ref is taken. – Efe FRK Apr 02 '21 at 16:12
  • It should always use the latest ref, but the effect won't be triggered when the ref changes. – Chris Apr 02 '21 at 18:15
0

By the way I found the answer, just generated all the links in useEffect and it made my job significantly easier:

useEffect(() => {
          const list = []
          for (var i=0;i<datas.length;i++){
               const t = datas[i].title
               const res = handleResize(i + 1)
               list.push(<li ref={resl[i + 1]} style={{top: "20px",left: res + "px"}}><Anchor href={/*datas[i].content?][1]*/ `/${t.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(i)}>{t}</a></Anchor></li>)
          }

          setLinks(list)
          console.log(datas[hovering])
     })

And then I returned it inside another html element.

Efe FRK
  • 177
  • 1
  • 13
0

I usually use useMemo instead of useEffect for this kind of matter.

const [change, setChange] = useState() //whatever your onclick value sets
const res = useMemo(() => {

                const w = globalThis.outerWidth
                const h = globalThis.outerHeight
                let index = 0
                for (let elem = 0;elem<allDataElems.length;elem++) {
                     if (allDataElems[elem] === name) {
                          index = elem + 1
                          break
                     }
                }
                var elSize = null
                try {
                     elSize = ulEl.current!.scrollTop + (ulEl.current!.firstElementChild!.scrollHeight * (index)) + index * 32 - 250
                } catch {
                     elSize = 0
                }
                return (w*28*3/1000)*(elSize/h)
        
      }, [change]) //dependency. whatever that will retrigger res to get new value

you can use res like how u wrote in the question from hereon.

Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • what's the difference of useMemo? – Efe FRK Apr 03 '21 at 07:18
  • https://stackoverflow.com/a/56029184/2822041 gives u an answer. Cleaner, predictable, less moving parts. UseEffect is used for side effects (e.g. calling a function after a render), useMemo usually used for calculations and returning values, and saving it in memory. Your code currently is calling the same function after EVERY re-render, which probably mess up your codes. – Someone Special Apr 03 '21 at 08:42