Recently, I had a project that needed to be rewritten from React to Next.js. I worked with individuals who weren't familiar with server components and how they work, so they kept asking me constantly:
💬 How do i know if this component is a server one or a client one?
My first answer (a silly one you may say 😕) was:
💬 Just check at the top of the file if it contains 'use client' or not" 😇
Just for those who didn't made the jump to NextJS yet, all components in NextJS are server by default so if you needed a client one, you have to add "use client" at the top of your file.
Later, they came to me again with another question:
Ok, we got it, when there is "use client" at the top, it is a client one but how comes this other component that doesn't have "use client" inside it claims to be one?
I was suggested to rename every server file into something explicit like ServerTagComponent
🤧
But then i remembered a video of Jack Herrington about state management in Next.JS with this kind of segmentation in his components:
Wouldn't it be cool to have something like this in your components 🤩?
So the general idea is to implement a higher order component that adds a label for each component that uses it.
The final result should be something like this:
And every component that needs it should have a withComponentIdentifier
in order to display these boxes.
Our HOC component
A Higher Order Component (HOC) is a function in React that takes a component and returns a new component with additional features or behaviors.
It's a way to reuse component logic and share functionalities among different parts of your application.
Let's start by creating our HOC file:
// components/ComponentIdentifier
import { ComponentType, FC } from 'react';
export function withComponentIdentifier<P extends object>(
WrappedComponent: ComponentType<P>
): ComponentType<P> {
const WithComponentIdentifier: FC<P> = (props) => {
const isClient = typeof window !== 'undefined';
const display = isClient ? 'client' : 'server';
return (
<div>
<p className={`hoc-text ${display}`}>This is a {display} component</p>
<div className='hoc-content'>
<WrappedComponent {...props} />
</div>
</div>
);
};
return WithComponentIdentifier;
}
This is a simple HOC component written in TypeScript, but the trick here is on this line:
const isClient = typeof window !== 'undefined';
Because a server component is rendered by the server then it shouldn't have a window property.
Usage
Now that we have our HOC component, let's use it. Create any tsx file that you want inside our app
folder. I chose to name mine client.tsx
then create a simple client component:
// app/client.tsx
'use client';
const ClientComponent = () => {
return <p>This is a simple client component</p>;
};
export default ClientComponent;
Then create another component called server.tsx
:
const ServerComponent = () => {
return <p>This is a server component</p>;
};
export default ServerComponent;
Finally, create a component that we will identify:
// app/unknown.tsx
const UnknownComponent = () => {
return <p>This is an unknown component</p>;
};
export default UnknownComponent;
We will use this component inside each file created previously to determine either it's a client or a server component.
Your final page.tsx
should be like this:
import ClientComponent from './client';
import ServerComponent from './server';
export default async function HomePage() {
return (
<>
<ClientComponent />
<ServerComponent />
</>
);
}
Now, let's use our HOC inside these components:
// app/client.tsx
'use client';
import { withComponentIdentifier } from '@/components/ClientOrServer';
const ClientComponent = () => {
return <p>This is a simple client component</p>;
};
export default withComponentIdentifier(ClientComponent);
And the server.tsx
:
import { withComponentIdentifier } from '@/components/ClientOrServer';
const ServerComponent = () => {
return <p>This is a server component</p>;
};
export default withComponentIdentifier(ServerComponent);
Just like that, we now have these cool boxes:
Use case
Although these components are clearly explicit, there is a scenario where you might not know if a component is nested inside a client or server component. This is where our Higher Order Component (HOC) proves to be truly useful. Let's consider a situation where we call our UnknownComponent within our ClientComponent (and remember to include our HOC in our UnknownComponent).
// app/client.tsx
'use client';
import UnknownComponent from './unknown';
const ClientComponent = () => {
return (
<>
<p>This is a simple client component</p>
<UnknownComponent />
</>
);
};
export default ClientComponent;
Notice that i removed the HOC from the ClientComponent resulting to identify just the unknown component.
Now, let's just move our UnknownComponent
inside our ServerComponent
:
// app/server.tsx
import UnknownComponent from './unknown';
const ServerComponent = () => {
return (
<>
<p>This is a server component</p>
<UnknownComponent />
</>
);
};
export default ServerComponent;
Here is the result:
Conclusion
With the usage of that HOC, everyone can now wrap components to determine if its a client one or a server one without asking all the time! 😄😇
Thanks for reading!
Top comments (0)