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 type { 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 process.env.NODE_ENV === 'development' ? (
<div>
<p className={`hoc-text ${display}`}>This is a {display} component</p>
<div className={`hoc-content ${display}`}>
<WrappedComponent {...props} />
</div>
</div>
) : (
<WrappedComponent {...props} />
);
};
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.
💡 By adding the line process.env.NODE_ENV === 'development'
we make sure that the HOC only displays on development mode. Read more about that here
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)