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
4 Answers
Reset to default 1Its 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>)
},[]);
- bind the events inside the useEffect only
useEffect(() => {
document.addEventListener('scroll', debounce(storeScroll), { passive: true });
}, []);
same way you can access navbar in useEffect
useEffect(() => { const active = document.getElementById('navbar'); }, [])
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745265089a4619407.html
评论列表(0条)