0

In react, setting state's value with the equal old's one is guaranteed not triggering render, right?

How about storing an array in state?
React compares the array by reference, not by values deeply. So setting a new array with exactly the same values still triggering render.

I created a component that listening a scroll event.
Thus I need to use useEffect to attach & detach the scroll event.

On every scroll event, it determines which children are visible within scroll view,
and save the indices (an array of index) of the children into a state, something like this:

const [index, setIndex]     = useState(-1);
const [indices, setIndices] = useState([]);

/* ... */

useEffect(() => {
   const handleScroll = () => {
      const newIndex  : number   = calculateFirstVisibleChild();
      const newIndices: number[] = calculateVisibleChildren();

      // save the result:
      setIndex(newIndex);     // trigger render only if the value changes (works)
      setIndices(newIndices); // always trigger render (works but inefficient)
   }

   window.addEventListener('scroll', handleScroll);
   return () => window.removeEventListener('scroll', handleScroll); // cleanup
}, []); // no dep, execute once at startup

The problem are:
How to set the indices with the same values without triggering the render.
If the scrolling distance is small, the visible children is remain the same, so the function still returning the same values.

I cannot comparing the indices & newIndices before passing into setIndices because it needs to be listed on the useEffect's dependencies, thus causing the event listener detached & attached back unnecessarily every index/indices changes.

Here the screenshot of the component i'm trying to make: enter image description here

Update: here the usage of the stored index/indices: At this moment i use the index (single level).
I'm going to upgrade my NavScroll to multi level after the indices problem solved.

// the render:
.....map((child, i) => (
   <ListGroupItem
      key={i}

      active={(i === index)} // here the actual highlight works! true: highlight, false: normal
   >
   </ListGroupItem>
.....

Update:
I will convert the indices array into comma separated string to avoid triggering unnecessary render and parse/convert back to array when render.
I know this is an ugly hack, but it works.
Please help me solving with the beauty way.

1
  • Simply create two effects. One for attaching/detaching the event listener on component mount/unmount, and one for tracking changes to your state variables. Commented May 20, 2021 at 6:04

2 Answers 2

1

I assume calculateVisibleChildren() returns an array, is that correct? If that's the case, then newIndices is essentially a new array even if all the elements are the same. So react will re-render.

Yet, I assume you are going to use the indices this way,

// render
return (
  <div>
    {indices.map(index => <SomeComponent  index={index} />)}
  </div>
);

If that's the case, you can wrap SomeComponent into memo(). i.e.

// SomeComponent.js
import { memo } from "react";

const SomeComponent = () => {
  return /* some components */;
}

export default memo(SomeComponent);

See the official doc about memo for more information.

Sign up to request clarification or add additional context in comments.

Comments

0

After I spent a day googling for the solution,
i found a useReducer hook. For me, it's the advanced version of useState.
The getter still the same as useState, but the setter has more control (cmiiw).

// const [activeIndices, setActiveIndices] = useState<number[]>([]); // bye-bye

const [activeIndices, setActiveIndices] = useReducer((indices: number[], newIndices: number[]): number[] => {
        if (deepEqual(newIndices, indices)) return indices; // already the same, use the old as by-reference
        return newIndices; // update with the new one
    }, []);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.