DEV Community

AILI Fida Aliotti Christino
AILI Fida Aliotti Christino

Posted on • Edited on

Client or Server component ?

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:

segmented_component

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:

expected_result

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;
}
Enter fullscreen mode Exit fullscreen mode

This is a simple HOC component written in TypeScript, but the trick here is on this line:

  const isClient = typeof window !== 'undefined';
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Then create another component called server.tsx:

const ServerComponent = () => {
  return <p>This is a server component</p>;
};

export default ServerComponent;
Enter fullscreen mode Exit fullscreen mode

Finally, create a component that we will identify:

// app/unknown.tsx
const UnknownComponent = () => {
  return <p>This is an unknown component</p>;
};

export default UnknownComponent;
Enter fullscreen mode Exit fullscreen mode

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 />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

client_server_result

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);
Enter fullscreen mode Exit fullscreen mode

And the server.tsx:

import { withComponentIdentifier } from '@/components/ClientOrServer';

const ServerComponent = () => {
  return <p>This is a server component</p>;
};

export default withComponentIdentifier(ServerComponent);
Enter fullscreen mode Exit fullscreen mode

Just like that, we now have these cool boxes:

boxes_result

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;
Enter fullscreen mode Exit fullscreen mode

Notice that i removed the HOC from the ClientComponent resulting to identify just the unknown component.

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;
Enter fullscreen mode Exit fullscreen mode

Here is the result:

final_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!

Buy Me A Coffee

Top comments (0)