cssreactjsvite

React JS. Content does not appear correctly


I have 2 files

Home.jsx

import React, { useState, useEffect } from 'react';
import './Home.css';

const HomePage = () => {
    const [visibleSections, setVisibleSections] = useState([]);

    useEffect(() => {
        const sections = ['header', 'description', 'button', 'footer'];
        let index = 0;

        const intervalId = setInterval(() => {
            if (index < sections.length) {
                setVisibleSections(prev => [...prev, sections[index]]);
                index++;
            } else {
                clearInterval(intervalId);
            }
        }, 1000); // Delay of 1 second between each section

        return () => clearInterval(intervalId);
    }, []);

    return (
        <div className="home-page">
            <header className={`section ${visibleSections.includes('header') ? 'visible' : ''}`}>
                <h1>Welcome to Chess Play</h1>
            </header>

            <div className={`section description ${visibleSections.includes('description') ? 'visible' : ''}`}>
                <p>
                    Experience the classic game of chess with our interactive platform. Whether you're a beginner or a seasoned player, our site offers a variety of features to enhance your chess skills.
                </p>
            </div>

            <main className={`section main-content ${visibleSections.includes('button') ? 'visible' : ''}`}>
                <button
                    className="play-button"
                    onClick={() => alert('Start Playing!')}
                >
                    Start Playing
                </button>
            </main>

            <footer className={`section ${visibleSections.includes('footer') ? 'visible' : ''}`}>
                <p>© 2023 Chess Play. All rights reserved.</p>
            </footer>
        </div>
    );
};

export default HomePage;

Home.css

.home-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    text-align: center;
}
.section {
    margin-bottom: 20px;
    transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

.section:not(.visible) {
    opacity: 0;
    transform: translateY(20px);
    pointer-events: none;
}

.section.visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

header h1 {
    font-size: 2.5em;
    margin-bottom: 20px;
}

.description {
    max-width: 600px;
}

.play-button {
    padding: 10px 20px;
    font-size: 18px;
    cursor: pointer;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 5px;
    transition: background-color 0.3s;
}

.play-button:hover {
    background-color: #45a049;
}
footer {
    margin-top: 40px;
    font-size: 0.9em;
    color: #666;
}

The main goal is the content on the page appear one by one block(Header -> Description -> Button -> Footer). However, the Description does not render, but Its interactable(I can copy the text, but i cant see it)

That is how it looks like after all animations: enter image description here

As you can see, the 2 block is not there.

Please, help me. Where is my mistake?

I tried play with tags and css but it did not work


Solution

  • Your problem relates to closures and the async nature of setState updates.

    Firstly, to fix the issue all you need to do is create another variable that is not enclosed by the updater function in setVisibleSections:

        const intervalId = setInterval(() => {
          if (index < sections.length) {
            const block = sections[index];
            setVisibleSections((prev) => [...prev, block]);
            index++;
          } else {
            clearInterval(intervalId);
          }
        }, 1000);
    

    Playground Link

    So the problem probably happens because the arrow function: (prev) => [...prev, sections[index]] has closed over the variable index. And, hence it has access to the most updated value of index.

    Now the sequence of events is this:

    First iteration of the interval : index is 0, setVisibleSections is called. Its callback is also called (suprisingly) and index is increased to 1.

    Now second time, index is 1, and setVisibleSections is called but its callback is not called immediately. Instead index is increased to 2 first. And now the callback is called, and it uses the updated the value of 2.

    This React behaviour is peculiar but that is how it is. As can be seen in the playground. React Docs clearly mentions that updates from setState can be made async to the UI, and that is what happens in this case. But I do not see any mention as to if the callback would be called async or immediately so this might still be expected behaviour.

    The code change above does not fix the ordering behavior, it only changes the code enough such that the behaviour does not cause an issue for you.

    PS: Also, you can remove the index from updater function and just do setVisibleSections((prev) => [...prev, sections[prev.length]]);