简体   繁体   中英

Why is useState and DOM not recreating/identifying/re-rendering the new state even with useState([...Array])?

I am trying to render and then re-render using array.map() an array of objects. The useEffect is called whenever the state(selected) changes for the options ('All','BACKEND','FRONTEND','ML').

The

  1. All Selected rendering all data 已选择所有选项

  2. Backend Selected showing "backendData" 已选择后端选项

  3. Frontend Selected etc etc在此处输入图像描述

MY ISSUE:

The rendering of the data is fine but I have added a basic animation which fades the elements in one by one using "react-awesome-reveal" library. I did the same with plain CSS so I don't think my error lies here.

The animation works if you refresh or direct towards the page or THE NUMBER OF ELEMENTS TO BE RE-RENDERED IS MORE THAN THE ELELEMNTS ON SCREEN THE LATER ELEMENTS WILL HAVE THE ANIMATION.

EG: If I go from "Frontend" to "Backend" then there are currently 2 elements in the DOM but now 3 need to be re-rendered so the first 2 elements do not carry the animation but the third does. It also works in the reverse where if I go from "All" 9 elements to "ML" 2 elements the elements are automatically rendered without the animation.

It seems like the DOM is looking at what is already rendered and deciding to not re-render the elements that are already there thus the FADE animation is not working.

Ideally I want the setData(...) to create a new state so that the array is mapped as if it the page was refreshed.

Here is the code snippet:

import {
  backendCourses,
  frontendCourses,
  mlCourses,
  allCourses,
} from "../../data";

export default function Courses() {
  const [selected, setSelected] = useState();
  const [data, setData] = useState(allCourses);

  const list = [
    { id: "all", title: "All" },
    { ....},
     ...
  ];

  useEffect(() => {
    switch (selected) {
      case "all":
        setData(Array.from(allCourses));
        break;
      case "backend":
        setData([...backendCourses]);
      case ....

  }, [selected]);

  return (
    <div className="courses" id="courses">
      <h1>Courses</h1>
      <ul>
        {list.map((item, index) => (
          <CoursesList
            ...
            active={selected === item.id}
            setSelected={setSelected}
          />
        ))}
      </ul>

      <div className="container">
        {data.map((item, index) => (
          <Fade ...>
            <div ...>
             <img ...>
            </div>
          </Fade>
        ))}
      </div>
    </div>
  );
}

//CoursesList Component

export default function CoursesList({ id, title, active, setSelected }) {
  return (
    <li
      className={active ? "coursesList active" : "coursesList"}
      onClick={() => setSelected(id)}
    >
      {title}
    </li>
  );
}

The mapping of the elements using Fade from "react-awesome-reveal" FULL-CODE.

<div className="container">
        {data.map((item, index) => (
          <Fade delay={index * 500}>
            <div className="item">
              <img src={item.img} alt={item.title} id={index}></img>
              <a href={item.url} target="_blank">
                <h3>{item.title}</h3>
              </a>
            </div>
          </Fade>
        ))}

Here is the full useEffect hook code. I initially thought I was not creating a new array only referencing it but have tried multiple ways to clone the array without success. The courses data for now is hard-coded and just read from a file.

 useEffect(() => {
    switch (selected) {
      case "all":
        setData(Array.from(allCourses));
        break;
      case "backend":
        setData([...backendCourses]);
        break;
      case "frontend":
        setData([...frontendCourses]);
        break;
      case "ml":
        setData([...mlCourses]);
        break;
      default:
        setData([...allCourses]);
    }
  }, [selected]);

Sorry for the long post but trying to be as detailed as I can.

It seems like the DOM is looking at what is already rendered and deciding to not re-render the elements that are already there thus the FADE animation is not working.

It's React that does that, not the DOM. That's part of its job: To avoid recreating DOM elements that already have the content you're trying to show.

If you want React to think you're providing new elements every time, even when in fact they're the same, give the elements a new key. At the moment, your map call isn't giving them a key at all (which it should be ), so React defaults to using the element's position in the list as a key.

Instead, provide a key on the Fade component and make sure it changes when selected changes, probably by using selected itself in the key:

return (
    <div className="container">
        {data.map((item, index) => (
            // I'm assuming `item.url` is unique within the array, adjust this
            // if not. (*Don't* use `index`, indexes make poor keys, see the
            // link above.
            //    vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
            <Fade key={selected + "::" + item.url} delay={index * 500}>
                <div className="item">
                    <img src={item.img} alt={item.title} id={index}></img>
                    <a href={item.url} target="_blank">
                        <h3>{item.title}</h3>
                    </a>
                </div>
            </Fade>
        ))}
    </div>
);

That way, the elements get reused correctly when the component is rendered for other reasons (because each of them has a stable key), but when you change selected , they all get replaced with new elements because the key changes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM