javascript - React how to wait for componentpage to render to call function - Stack Overflow

I need to modify the data-scroll attribute of the first div I return here. Problem seems to be a race h

I need to modify the data-scroll attribute of the first div I return here. Problem seems to be a race hazard - active is null because the dom hasn't loaded yet. How do I deal with this? async/await? I'm not querying something and I won't get a response. How do I wait until the Header is rendered?

this generates TypeError: Cannot read property 'dataset' of null:

import { Link } from 'react-router-dom';
import * as ROUTES from '../constants/routes';

export default function Header() {
  const active = document.getElementById('navbar');

  const debounce = (fn) => {
    let frame;
    return (...params) => {
      if (frame) {
        cancelAnimationFrame(frame);
      }
      frame = requestAnimationFrame(() => {
        fn(...params);
      });
    };
  };
  const storeScroll = () => {
    if (window.scrollY > 80) {
      active.dataset.scroll = window.scrollY;
    }
    if (window.scrollY <= 80) {
      active.dataset.scroll = 0;
    }
  };

  // Listen for new scroll events
  document.addEventListener('scroll', debounce(storeScroll), { passive: true });

  // Update scroll position for first time
  storeScroll();
  return (
    <div id="navbar" data-scroll="0">
      <header className="fixed flex top-0 scroll:bg-blue-500 bg-transparent items-center justify-center p-5 w-full">
        <div className="container mx-auto max-w-screen-lg h-full">
          <div className="flex justify-between h-full">
            <div className="text-black text-center flex items-center align-items cursor-pointer">
              <h1 className="flex justify-center w-full">
                <Link to={ROUTES.TEST} className="font-bold text-lg" aria-label="home">
                  HOME
                </Link>
              </h1>
            </div>
          </div>
        </div>
      </header>
    </div>
  );
}

I need to modify the data-scroll attribute of the first div I return here. Problem seems to be a race hazard - active is null because the dom hasn't loaded yet. How do I deal with this? async/await? I'm not querying something and I won't get a response. How do I wait until the Header is rendered?

this generates TypeError: Cannot read property 'dataset' of null:

import { Link } from 'react-router-dom';
import * as ROUTES from '../constants/routes';

export default function Header() {
  const active = document.getElementById('navbar');

  const debounce = (fn) => {
    let frame;
    return (...params) => {
      if (frame) {
        cancelAnimationFrame(frame);
      }
      frame = requestAnimationFrame(() => {
        fn(...params);
      });
    };
  };
  const storeScroll = () => {
    if (window.scrollY > 80) {
      active.dataset.scroll = window.scrollY;
    }
    if (window.scrollY <= 80) {
      active.dataset.scroll = 0;
    }
  };

  // Listen for new scroll events
  document.addEventListener('scroll', debounce(storeScroll), { passive: true });

  // Update scroll position for first time
  storeScroll();
  return (
    <div id="navbar" data-scroll="0">
      <header className="fixed flex top-0 scroll:bg-blue-500 bg-transparent items-center justify-center p-5 w-full">
        <div className="container mx-auto max-w-screen-lg h-full">
          <div className="flex justify-between h-full">
            <div className="text-black text-center flex items-center align-items cursor-pointer">
              <h1 className="flex justify-center w-full">
                <Link to={ROUTES.TEST} className="font-bold text-lg" aria-label="home">
                  HOME
                </Link>
              </h1>
            </div>
          </div>
        </div>
      </header>
    </div>
  );
}
Share Improve this question asked Jun 1, 2021 at 9:17 LauraLaura 3672 gold badges6 silver badges18 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 1

Its not a good practice to target a underlying DOM in react directly . We can use the ref to do that.

Also you are adding an eventListener which does not get removed correctly when the ponent unmounts . so to make sure you have the element when the rendering is plete you can make use of the useEffect hook which guarantees that it is ran after the DOM is painted.

So you need to have your eventListener code to be move in the useEffect hook . Here is the updated code using Ref and useEffect .

import {Link} from 'react-router-dom';
import * as ROUTES from '../constants/routes';
import {useRef, useEffect} from 'react';

export default function Header() {
  const navbarRef = useRef();

  const debounce = (fn) => {
    let frame;
    return (...params) => {
      if (frame) {
        cancelAnimationFrame(frame);
      }
      frame = requestAnimationFrame(() => {
        fn(...params);
      });
    };
  };

  const storeScroll = () => {
    if (window.scrollY > 80) {
      navbarRef.current.dataset.scroll = window.scrollY;
    }
    if (window.scrollY <= 80) {
      navbarRef.current.dataset.scroll = 0;
    }
  };

  useEffect(() => {
    // Update scroll position for first time
    storeScroll();
    
    // attact the event listener
    window.addEventListener('scroll', debounce(storeScroll), {passive: true});
    
    // remove the event listener when the ponent is unmounted
    return () =>
      window.removeEventListener('scroll', debounce(storeScroll), {
        passive: true,
      });
  }, []);

  return (
    <div id="navbar" ref={navbarRef} data-scroll="0">
      <header className="fixed flex top-0 scroll:bg-blue-500 bg-transparent items-center justify-center p-5 w-full">
        <div className="container mx-auto max-w-screen-lg h-full">
          <div className="flex justify-between h-full">
            <div className="text-black text-center flex items-center align-items cursor-pointer">
              <h1 className="flex justify-center w-full">
                <Link
                  to={ROUTES.TEST}
                  className="font-bold text-lg"
                  aria-label="home"
                >
                  HOME
                </Link>
              </h1>
            </div>
          </div>
        </div>
      </header>
    </div>
  );
}

You'll need to use a ref to "catch" the DOM element.

const debounce = (fn) => {
  let frame;
  return (...params) => {
    if (frame) {
      cancelAnimationFrame(frame);
    }
    frame = requestAnimationFrame(() => {
      fn(...params);
    });
  };
};

const storeScroll = (element) => () => {
  if (window.scrollY > 80) {
    active.dataset.scroll = window.scrollY;
  }
  if (window.scrollY <= 80) {
    active.dataset.scroll = 0;
  }
};

export default function Header() {
  const addScrollListener = (element) => {
    if (!element) { 
      return null;
    }

    document.addEventListener('scroll', debounce(storeScroll(element)), { passive: true });
    storeScroll(element);
  }

  return (
    <div id="navbar" data-scroll="0" ref={addScrollListener}>
      ...
    </div>
  );
}

Even though you can use document.getElementById, it is discouraged in React, and the idiomatic way to get references to DOM elements is through refs.

Pulling the functions out of the ponent is not strictly necessary, just a practice I'd remend.

See Refs and the DOM.

You can use useEffect hook for that:

import { useEffect } from 'react'; 

useEffect(()=>{
  // what should happen after the initial render
  const active = document.getElementById('navbar');
  active.setAttribute("data-scroll", <your_value>)
},[]);
  1. bind the events inside the useEffect only
useEffect(() => {
  document.addEventListener('scroll', debounce(storeScroll), { passive: true });
}, []);
    
  1. same way you can access navbar in useEffect

     useEffect(() => {
         const active = document.getElementById('navbar');
     }, [])
    

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745265089a4619407.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信