Reputation: 2897
I have a simple component which performs these actions:
window.scrollY
scroll
eventListener to window to update stateabsolute
positionconst App = () => {
// state to keep track of how many px scrolled
const [scroll, setScroll] = useState(window.scrollY);
const handleScroll = () => setScroll(window.scrollY);
// set up listener on window to update scroll state on scroll
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
// create 10 dots with random positions
const dots = [];
for (let x = 0; x < 10; x++) {
dots.push(
<Dot
key={x}
topValue={getRandomIntInclusive(1, 199)}
leftValue={getRandomIntInclusive(1, 99)}
/>
);
}
return (
<div className="App">
{dots}
<Triangle heightValue={scroll / 10} />
</div>
);
};
When I scroll down, the only thing I want to happen is the Triangle
components height recalculates / re-renders. The originally rendered dots will not update or change.
As you might guess, the dots get re-rendered every scroll event, which is not only horrible for performance, but it causes the dots to have a new random position every single scroll event.
Triangle
to re-render using the updated scroll
value, but not have the Dot
components re-render?Dot
components on scroll (color, for example), but not update the random top
and left
positions that were created on the initial Mount, how would I do that?I can accomplish something like this quite easily with vanilla JS, but I can't quite figure out the React way.
Complete working sandbox of this issue: https://codesandbox.io/embed/hopeful-greider-jb3td
Upvotes: 4
Views: 2289
Reputation: 281726
What you need to do is calculate the dots position once and save it in an array so as to not recalculate them on each and every render when the scroll position changes
You can either use useEffect
with empty deps
array to do that like
// main component
const App = () => {
// state to keep track of how many px scrolled
const [scroll, setScroll] = useState(window.scrollY);
const handleScroll = () => setScroll(window.scrollY);
const [posArr, setPos] = useState([]);
useEffect(() => {
const pos = [];
for (let x = 0; x < 10; x++) {
pos.push({ topValue: getRandomIntInclusive(1, 199), leftValue: getRandomIntInclusive(1, 99)})
}
setPos(pos);
}, [])
// set up listener on window to update scroll state on scroll
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
// create 10 dots with random positions
const dots = [];
for (let x = 0; x < 10; x++) {
dots.push(
<Dot
key={x}
topValue={posArr[x] && posArr[x].topValue}
leftValue={posArr[x] && posArr[x].leftValue}
/>
);
}
return (
<div className="App">
<br />
<h2>Scroll to make triangle grow</h2>
<br />
<h4>Ideally, the dots would not rerender on scroll</h4>
<Debug>Px scrolled: {scroll}</Debug>
{dots}
<Triangle heightValue={scroll / 10} />
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
or initialise the state by using a callback function to useState
// main component
const App = () => {
// state to keep track of how many px scrolled
const [scroll, setScroll] = useState(window.scrollY);
const handleScroll = () => setScroll(window.scrollY);
// set up listener on window to update scroll state on scroll
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
const [posArr, setPos] = useState(() => {
const pos = [];
for (let x = 0; x < 10; x++) {
pos.push({ topValue: getRandomIntInclusive(1, 199), leftValue: getRandomIntInclusive(1, 99) })
}
return pos;
});
// create 10 dots with random positions
const dots = [];
for (let x = 0; x < 10; x++) {
dots.push(
<Dot
key={x}
topValue={posArr[x].topValue}
leftValue={posArr[x].leftValue}
/>
);
}
return (
<div className="App">
<br />
<h2>Scroll to make triangle grow</h2>
<br />
<h4>Ideally, the dots would not rerender on scroll</h4>
<Debug>Px scrolled: {scroll}</Debug>
{dots}
<Triangle heightValue={scroll / 10} />
</div>
);
};
Upvotes: 3