When the React component's state is updated, an animation is triggered in a subcomponent connected to the parent component

The ProjectsSection component is responsible for rendering a document section with a header and cards. The goal is to trigger a header animation only when the header becomes visible on the screen for the first time and have it run once. However, adding a state to the ProjectsSection component resulted in the header animation running on every state change. Surprisingly, only a part of the animation (the letters/spans that are children of h2) actually moves, while brackets/pseudo elements on h2 do not. CSS modules are used for styling in this project.

Attempts were made to wrap the SectionHeader component in React.memo, but it did not solve the issue.

Main component:

const ProjectsSection = () => {
  const [openProjectCardId, setOpenProjectCardId] = useState('');
  
  return (
    <section id="projects" className="section">
      <div className="container">
        <SectionHeader>Projects</SectionHeader>

        <div className={styles.content_wrapper}>
          {projects.map((project, ind) => (
            <Project
              key={project.id}
              project={project}
              ind={ind}
              isOpen={openProjectCardId === project.id}
              setOpenProjectCardId={setOpenProjectCardId}
            />
          ))}
        </div>
      </div>
    </section>
  );
};

SectionHeader component:

const SectionHeader = ({ children }) => {
  const headerRef = useIntersection(
    styles.sectionHeader__isVisible,
    { rootMargin: '0px 0px -100px 0px', threshold: 1 },
  );

  const textToLetters = children.split('').map((letter) => {
    const style = !letter ? styles.space : styles.letter;
    return (
      <span className={style} key={nanoid()}>
        {letter}
      </span>
    );
  });

  return (
    <div className={styles.sectionHeader_wrapper} ref={headerRef}>
      <h2 className={styles.sectionHeader}>{textToLetters}</h2>
    </div>
  );
};

Css

.sectionHeader_wrapper {
  position: relative;
  // other properties

  &:before {
    display: block;
    content: ' ';
    position: absolute;
    opacity: 0;
    // other properties
  }

  &:after {
    display: block;
    content: ' ';
    position: absolute;
    opacity: 0;
    // other properties
  }
}

.sectionHeader {
  position: relative;
  display: inline-block;
  overflow: hidden;
  // other properties
}

.letter {
  position: relative;
  display: inline-block;
  transform: translateY(100%) skew(0deg, 20deg);
}

.sectionHeader__isVisible .letter {
  animation: typeletter .25s ease-out forwards;
}

useIntersection hook

const useIntersection = (
  activeClass,
  { root = null, rootMargin = '0px', threshold = 1 },
  dependency = [],
  unobserveAfterFirstIntersection = true
) => {
  const elementRef = useRef(null);

  useEffect(() => {
    const options: IntersectionObserverInit = {
      root,
      rootMargin,
      threshold,
    };

    const observer = new IntersectionObserver((entries, observerObj) => {
      entries.forEach((entry) => {
        if (unobserveAfterFirstIntersection) {
          if (entry.isIntersecting) {
            entry.target.classList.add(activeClass);
            observerObj.unobserve(entry.target);
          }
        } else if (entry.isIntersecting) {
          entry.target.classList.add(activeClass);
        } else {
          entry.target.classList.remove(activeClass);
        }
      });
    }, options);

    // if (!elementRef.current) return;
    if (elementRef.current) {
      observer.observe(elementRef.current);
    }
  }, [...dependency]);

  return elementRef;
};

Answer №1

After much investigation, I have successfully resolved the issue at hand. The root cause appeared to be the textToLetters variable being recreated whenever the parent component's state changed. By implementing useMemo to maintain this value, the animation no longer gets triggered.

const convertTextToLetters = (text) =>
  text.split('').map((letter) => {
    const style = !letter ? styles.space : styles.letter;
    return (
      <span className={style} key={nanoid()}>
        {letter}
      </span>
    );
  });

const SectionHeading= ({ children }) => {
  const headerRef = useIntersection(
    styles.heading__isVisible,
    { rootMargin: '0px 0px -100px 0px', threshold: 1 },
    [children]
  );
  const letters = React.useMemo(() => convertTextToLetters(children), [children]);

  return (
    <div className={styles.heading_wrapper} ref={headerRef}>
      <h2 className={styles.heading}>{letters}</h2>
    </div>
  );
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Order of stacked divs with absolute positioning

I'm struggling to make a photo expand when hovered over without it expanding under the existing content. My current HTML and CSS code is as follows: .userCard {width:360px;height:180px;border:1px solid black;float:left;margin:10px;position:relative ...

Using an array of images with CSS and displaying them in HTML

Is there a way to use one class for 3 buttons, each with its own unique background image? I wanted to create an array of background images in CSS and then retrieve them individually for each button. However, after some research, it seems that CSS does not ...

IE11 Experiencing Overflow Issue with List Item Pseudo Element Numbers

Having a simple unordered list of links and using pseudo elements to generate numbers for the list, I encountered an issue with centering the number vertically within its circle background in Internet Explorer 11. Here is the HTML code: <ul> < ...

Utilizing Refs in Material-UI 4 with React 16.8 and TypeScript 3.5 - A Step-by-Step Guide

Currently in the process of migrating to newer versions, encountering a forwardRef issue with multiple components. While there are plenty of examples for Function components using forwardRef, the same isn't clear for HOC class components and large nes ...

What steps should I take to resolve the hydration error occurring in the first div of a blank "hello world" webpage

Currently, I am diving into learning Next.js (version 13/14) and have been following some tutorials on YouTube. However, I encountered an issue with my first page when the root HTML element is a <div>. These tutorials are utilizing <div id="root"& ...

Adding a new tab on button click with React js and Bootstrap 4 - a step-by-step guide

Is there a way to dynamically add a tab when a button is clicked? I've attempted various examples, but none of them have been successful for me. For instance: Initially, there are 2 bootstrap tabs named Tab1 and Tab2. There is also an "Add Tab" butt ...

Animating the height with jQuery can result in the background color being overlooked

For those curious about the issue, check out the JSFiddle. There seems to be an anomaly where the background of the <ul> element disappears after the animation on the second click. Oddly enough, even though Firebug shows that the CSS style is being a ...

Resolving CSS glitches that mysteriously vanish in Internet Explorer versions 8 and 9

My website was created using WordPress, with various plugins added to enhance its functionality. However, I have encountered an issue when viewing the site in Internet Explorer 8 & 9. The visual effects of the plugins seem to lose all CSS styling in IE bro ...

Unable to trigger a change event on a React component with Material UI Higher Order Component (HOC

I am facing an issue with simulating and observing clicks on a High Order Component (HOC) in Material UI. The problem seems to be that passing the onChange property does not trigger the associated handleChange event handler. Here is the code snippet causi ...

Achieving a centrally aligned flex container with left-aligned flex items

My goal is to center the flex items, but when they go onto a second line, I want item 5 (from image below) to be positioned under item 1 instead of centered in the parent container. https://i.sstatic.net/GhD1E.png Here's a sample of my current setup ...

Unable to locate the React native Android directory

Check out this image: I'm having trouble locating the Android File in my React Native project folder. I need to convert my project into a .APK file, but without the Android folder, I can't accomplish it. Can anyone offer assistance? I attempted ...

Changing the text field label color with React and Material-UI when it's in focus

https://i.stack.imgur.com/xpIAs.png My goal is to make the text "First Name" appear in red color. To achieve this, I used the following code snippet: <TextField id="outlined-basic" label="First name" variant="outlined" ...

Leveraging ag-grid within a React application

Within my application, there exists a structure with four distinct tabs. Each tab is intended to display a unique dataset, yet at this time, only two datasets have been defined. Upon executing the code provided below, an error is produced indicating an i ...

Using React Material UI in VSCode with TypeScript significantly hampers autocompletion speed

When including the "@mui/material", Visual Studio Code may become unresponsive, leading to Typescript warnings appearing after 10-15 seconds instead of the usual less than 10 milliseconds. For example: import { Button } from '@mui/material&a ...

Tips for Updating the Background Color of an Accordion in react-bootstrap 5

Provide a summary of the issue I have been exploring the react-bootstrap 5 documentation and am struggling to add custom CSS styles, specifically setting a background-color. Explain your attempt so far My current approach is <Accordio ...

Replace Font Awesome icon with a background image

Looking to replace a Font Awesome icon with a background image using only CSS. Current Setup: .btn.btn-default.bootstrap-touchspin-up:before { font-family: Fontawesome; content: "\f067"; font-size: 14px; } Desired Outcome: .btn.btn-de ...

The Bootstrap carousel comes with a brilliant feature! Conceal those 'previous' and 'next' buttons when you reach the first and last slide. However, it seems like the 'slide' event is not functioning as

I have implemented a standard Bootstrap carousel on my website: <div id="myCarousel" class="carousel slide" data-ride="carousel"> <!-- Indicators --> <ol class="carousel-indicators"> <li data-target="#myCarousel" data- ...

Unpacking the information in React

My goal is to destructure coinsData so I can access the id globally and iterate through the data elsewhere. However, I am facing an issue with TypeScript on exporting CoinProvider: Type '({ children }: { children?: ReactNode; }) => void' is no ...

Tips for concealing overflow x on a responsive website

Could use some help here. Whenever I switch to responsive mode, the overflow x feature gets enabled and I'm at a loss on how to disable it. I've attempted using @media to disable overflow but unfortunately it's not working as expected. ...

Using React hooks to update the state of an array from one component to another

I am currently working on a MERN blog website project and I've encountered an issue while trying to update comments on a post. I'm editing the comment from another component but facing difficulty in updating the state after making changes. Would ...