How to apply CSS animations to React components that re-render

Viewed 44

I am attempting to reproduce the sliding image effect seen on this website https://chiwawa.es/en/.

I can see how it works on this website (see changeImages function in 63c2acc.js; however, getting this to work in React is proving difficult for me.

To illustrate what I have managed to do, I've published https://dskdirhhwk.vercel.app/. Solved - (this link now correctly shows animations thanks to answer below)

The difficulty I'm running into is animating the images. The Sections are getting re-rendered by React upon state update, affecting how I apply the classes to the markup. I used Reacts Profiler to verify these are updates, not remounts.

I initially had the Intersection Observer outside the Section component, but this didn't appear to work. I also had a simple setState mechanism and have since built it into a reducer, so that I could add the active class after the other classes.

I am pulling my hair out here. I want a few hints and a push in the right direction!

This is on Next.js. Here is my code.

import React, {
    Fragment,
    useState,
    forwardRef,
    useEffect,
    useLayoutEffect,
    useCallback,
    useContext,
    useRef,
    useMemo,
} from 'react';

import cx from 'classnames';
// yarn add classnames


const useIntersect = ({root = null, rootMargin, threshold = 0}) => {
    const [entry, updateEntry] = useState({});
    const [node, setNode] = useState(null);

    const observer = useRef(null);

    useEffect(() => {
        if (observer.current) observer.current.disconnect();

        observer.current = new IntersectionObserver(([entry]) => updateEntry(entry), {
            root,
            rootMargin,
            threshold,
        });

        const {current: currentObserver} = observer;

        if (node) currentObserver.observe(node);

        return () => currentObserver.disconnect();
    }, [node]);

    return [setNode, entry];
};

const Home = () => {
    const initialState = {active: 'teal', current: 'teal'};

    const [state, dispatch] = React.useReducer(reducer, initialState);

    function reducer(state, {section, type}) {
        switch (type) {
            case 'reset':
                return initialState;
            case 'changeSection':
                return {
                    ...state,
                    previous: state?.current,
                    current: section,
                };
            case 'makeActive':
                return {
                    ...state,
                    active: section,
                };
            default:
                return state;
        }
    }

    const activeSection = (section) => {
        dispatch({section: section, type: 'changeSection'});

        setTimeout(() => {
            dispatch({type: 'makeActive', section: section});
        }, 1000);
    };

    const Section = ({sectionID, className, children}) => {
        const [ref, entry] = useIntersect({
            threshold: '0.7',
        });

        useEffect(() => {
            if (state?.current == sectionID) return;

            if (entry.isIntersecting) {
                activeSection(sectionID);
            }
        }, [entry]);

        const childrenWithProps = React.Children.map(children, (child, index) => {
            if (React.isValidElement(child)) {
                if (index == 0)
                    return React.cloneElement(child, {
                        className: cx(
                            'fixed top-0 w-1/2 h-screen transition-all duration-[1000ms] place-items-center place-content-center transform-gpu',
                            {
                                'translate-y-[-100vh]': sectionID != state?.active,
                                'z-40 translate-y-0': sectionID == state?.current && sectionID == state?.active,
                            }
                        ),
                        data: 'observable',
                        id: sectionID,
                    });
            }
            return child;
        });

        return (
            <section className={className} ref={ref}>
                {childrenWithProps}
            </section>
        );
    };

    return (
        <>
            <div className="fixed bottom-0 z-50 w-1/2 opacity-50">
                <pre>{JSON.stringify(state, null, 2)}</pre>
            </div>

            <Section sectionID="teal" className="bg-teal-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=1" />
                </div>

                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="blue" className="bg-blue-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=2" />
                </div>
                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="orange" className="bg-orange-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=3" />
                </div>
                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="pink" className="bg-pink-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=4" />
                </div>

                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>
        </>
    );
};

export default Home;
1 Answers

You should (almost) never ever ever declare a component inside of another functional component. Doing so essentially creates an entirely new React component class every render, which means it is impossible for React to reconcile which component is which between renders. Try moving your <Section> functional component definition out of the scope of <Home>.

Related