Components Structure (Client/Server)
Unanswered
Romain posted this in #help-forum
RomainOP
Hello everyone,
I'm writing about component creation and how to deal with client and server components. I have a problem. My header component needs an animation that requires it to be a client component. However, inside the header component, I have children that need to be server components. The issue is that I'm declaring my header component as a client component because of the Framer Motion animation I'm applying to it. I really don't know how to fix this issue and how to structure my code so I can have my header animation and still have the header child components be server components.
Can anyone help me resolve this? Thank you!
I'm writing about component creation and how to deal with client and server components. I have a problem. My header component needs an animation that requires it to be a client component. However, inside the header component, I have children that need to be server components. The issue is that I'm declaring my header component as a client component because of the Framer Motion animation I'm applying to it. I really don't know how to fix this issue and how to structure my code so I can have my header animation and still have the header child components be server components.
Can anyone help me resolve this? Thank you!
'use client'
import React, {useState} from "react";
import {motion, useMotionValueEvent, useScroll} from "framer-motion";
import HeaderTopBar from "@/components/layout/header/header-top-bar";
import HeaderBottomBar from "@/components/layout/header/header-bottom-bar";
export default function Header() {
const {scrollY} = useScroll();
const [hidden, setHidden] = useState(false);
useMotionValueEvent(scrollY, 'change', (latest) => {
const previous = scrollY.getPrevious();
if (previous !== undefined && latest > previous && latest > 150) {
setHidden(true);
} else {
setHidden(false);
}
});
return (
<motion.header
variants={{
visible: {y: 0},
hidden: {y: '-100%'}
}}
transition={{duration: 0.3, ease: 'easeInOut'}}
animate={hidden ? 'hidden' : 'visible'}
className="sticky top-0 z-50 w-screen bg-white"
>
<HeaderTopBar/>
<HeaderBottomBar/>
</motion.header>
)
}21 Replies
I think you can make the raw item to be rendered on server and what needs to be animated just abstract it into a client component and pass the children to the client from the server if that makes any sense
American Crow
layout / page.tsx
// parent component is a server component
...
return (
<div>
<Header> // client component, pass children
<HeaderTopBar /> // will be a server component
<HeaderBottomBar /> // will be a server component
</Header>
</div>
)RomainOP
<OverlayProvider>
<SearchProvider>
<Announcement />
<Header />
<main>{children}</main>
<Footer />
<Overlay />
</SearchProvider>
</OverlayProvider>The thing is that in my header component, all the components that I'm adding becomes client components. For example in the <HeaderTopBar /> I need to import my <Cart/> components that needs next/headers and it has to be a server components, but I keep gettings errors about it. I can't figure this out lol
American Crow
You have to look at the component in which you import others components
If your parent component is a client component and you directly import other components into it. They also become client components.
You have to work with children as shown above to avoid that
If your parent component is a client component and you directly import other components into it. They also become client components.
You have to work with children as shown above to avoid that
"use client" is a boundary it says "components below me / imported by me, are client components"RomainOP
// Header.tsx
export default function Header() {
return (
<HeaderWrapper>
<HeaderTopBar/>
<HeaderBottomBar/>
</HeaderWrapper>
)
}
// headerWrapper.tsx
export function HeaderWrapper({children}: { children: React.ReactNode }) {
const {scrollY} = useScroll();
const [hidden, setHidden] = useState(false);
useMotionValueEvent(scrollY, 'change', (latest) => {
const previous = scrollY.getPrevious();
if (previous !== undefined && latest > previous && latest > 150) {
setHidden(true);
} else {
setHidden(false);
}
});
return (
<motion.header
variants={{
visible: {y: 0},
hidden: {y: '-100%'}
}}
transition={{duration: 0.3, ease: 'easeInOut'}}
animate={hidden ? 'hidden' : 'visible'}
className="sticky top-0 z-50 w-screen bg-white"
>
{children}
</motion.header>
)
}So something like this would fix my issue?
And Header Wrapper would use the 'use client'
RomainOP
I encounter this error: TypeError: Cannot read properties of null (reading 'useReducer')
Layout.tsx
Header.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={`${cormorant.variable}`}>
<body>
<OverlayProvider>
<SearchProvider>
<Announcement />
<Header />
<main>{children}</main>
<Footer />
<Overlay />
</SearchProvider>
</OverlayProvider>
</body>
</html>
);
}Header.tsx
import HeaderWrapper from "@/components/layout/header/header-wrapper";
import HeaderTopBar from "@/components/layout/header/header-top-bar";
import HeaderBottomBar from "@/components/layout/header/header-bottom-bar";
export default function Header() {
return (
<HeaderWrapper>
<HeaderTopBar/>
<HeaderBottomBar/>
</HeaderWrapper>
)
}HeaderWrapper.tsx
'use client';
import React, {useState} from "react";
import {motion, useMotionValueEvent, useScroll} from "framer-motion";
export default function HeaderWrapper({children,}: { children: React.ReactNode }) {
const {scrollY} = useScroll();
const [hidden, setHidden] = useState(false);
useMotionValueEvent(scrollY, 'change', (latest) => {
const previous = scrollY.getPrevious();
if (previous !== undefined && latest > previous && latest > 150) {
setHidden(true);
} else {
setHidden(false);
}
});
return (
<motion.header
variants={{
visible: {y: 0},
hidden: {y: '-100%'}
}}
transition={{duration: 0.3, ease: 'easeInOut'}}
animate={hidden ? 'hidden' : 'visible'}
className="sticky top-0 z-50 w-screen bg-white"
>
{children}
</motion.header>
)
}HeaderTopBar.tsx
import Link from "next/link";
import {Logo} from "@/components/icons";
import IconNavigation from "@/components/layout/header/icon-navigation";
import CountrySelector from "@/components/layout/header/country-selector";
import MobileNavigation from "@/components/layout/navigation/mobile-navigation";
export default function HeaderTopBar() {
return (
<div className="relative z-50 flex flex-row items-center justify-center border-b border-black/10 bg-white px-4 py-8 md:px-16">
<div className="flex flex-1 items-center justify-start">
<CountrySelector/>
<MobileNavigation/>
</div>
<div className="flex flex-1 items-center justify-center">
<Link href={'/'}>
<Logo className='text-black'/>
</Link>
</div>
<IconNavigation/>
</div>
);
}HeaderBottomBar.tsx
import Navigation from "@/components/layout/navigation";
import Search from "@/components/search";
export default function HeaderBottomBar() {
return (
<div className="relative z-40 hidden w-screen md:flex">
<Navigation/>
<Search/>
</div>
);
}American Crow
Gotta simplify to locate the error better might be in your providers.
Test if the error is within header
Test if the error is within header
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={`${cormorant.variable}`}>
<body>
<HeaderWrapper>
<HeaderTopBar/>
<HeaderBottomBar/>
</HeaderWrapper>
<main>{children}</main>
</body>
</html>
);
}RomainOP
It's within one of this two components, I still have the same error
American Crow
comment each out,test which one it is
RomainOP
Inside my header top bar I have a Search button to toggle the search bar located inside the header bottom bar. I have setup a Context Provider in order to manage the state of the button
I think that's the issue
SearchContext.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { OverlayContext } from './overlayContext';
type SearchContextType = {
isSearchBarVisible: boolean;
toggleSearchBar: () => void;
};
export const SearchContext = createContext<SearchContextType>({
isSearchBarVisible: true,
toggleSearchBar: () => {}
});
export default function SearchProvider({ children }: { children: React.ReactNode }) {
const [isSearchBarVisible, setSearchBarVisible] = useState(false);
const { isOverlayVisible, toggleOverlay } = useContext(OverlayContext);
const toggleSearchBar = () => {
const newSearchBarState = !isSearchBarVisible;
setSearchBarVisible(newSearchBarState);
if (newSearchBarState && !isOverlayVisible) {
toggleOverlay();
} else if (!newSearchBarState && isOverlayVisible) {
toggleOverlay();
}
};
useEffect(() => {
if (!isOverlayVisible && isSearchBarVisible) {
setSearchBarVisible(false);
}
}, [isOverlayVisible]);
return (
<SearchContext.Provider value={{ isSearchBarVisible, toggleSearchBar }}>
{children}
</SearchContext.Provider>
);
}