Sometimes, you may want to trigger an animation for a specific section based on whether the user has switched to that section. Here’s how you can achieve this:
Usage
By using this hook, you can run animations based on scroll direction while maintaining consistency between transitions. In certain scenarios, you may want to trigger different animations depending on the section you’re navigating from or to or even the direction if going forward or backward. We'll be building this hook in this post.
// SecondSection.tsx
import { Section } from './enums.ts'
export default function SecondSection() {
const { active } = useSection(
() => ({
section: Section.Second,
enter(back) {
// fade-in
if(!back) ... // when coming from previous (1 -> 2)
else ... // when coming from next (3 -> 2)
},
leave(back) {
// fade-out
if(!back) ... // when going forward (2 -> 3)
else ... // when going backward (2 -> 1)
},
}),
[],
);
return ...
}
Configuration
1. Define Sections as an Enum
First, create an enum to represent your sections. For example:
// enums.ts
enum Section {
First,
Second,
Third,
}
2. Global State Management
To manage global state, consider using a library like zustand. Here’s an example of setting up a store:
// store.ts
import { create } from 'zustand'
const useStore = create((set) => ({
section: Section.First,
setSection: (section) => set((state) => ({ section })),
}))
3. Watch Section Changes
Create a custom hook called usePrevious that stores the previous value and triggers an effect when the section changes:
// usePrevious.ts
import { useEffect, useRef } from "react";
export default function usePrevious<T extends any[]>(fn: (prev: T) => any, deps: T) {
const previousDepsRef = useRef<T>(deps);
useEffect(() => {
const eject = fn(previousDepsRef.current);
previousDepsRef.current = deps;
return eject;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}
4. Create the useSection Hook:
Finally, implement the useSection hook with four events: enter
, leave
, enterBack
, and leaveBack
. This hook will help you manage section transitions:
// useSection.ts
import { DependencyList, useMemo, useState } from "react";
import usePrevious from "@/hooks/usePrevious";
import { useStore } from "@/store";
import { Section } from "@/ts/enums";
type Config = {
section: Section;
enter?: (from: Section, back: boolean) => Function | void;
leave?: (to: Section, back: boolean) => Function | void;
};
export default function useSection(
getConfig: () => Config,
deps: DependencyList,
) {
const section = useStore((s) => s.section);
// eslint-disable-next-line react-hooks/exhaustive-deps
const config = useMemo(getConfig, deps);
const [target] = useState<Section>(config.section);
// same as useEffect but with previous values
usePrevious<[Section, Config | undefined]>(
([fromSection]) => {
if (section === target) {
const back = target < fromSection;
return config.enter?.(fromSection, back);
}
if (fromSection === target) {
const back = target > section;
return config.leave?.(section, back);
}
},
[section, config],
);
const active = useMemo(() => section === target, [section, target]);
return { section, active };
}
Feel free to explore the implementation on my portfolio website. Let me know if you have any questions!