Skip to content

Exercises

Server Components and styled-components

Let's get some practice with Server and Client Components!

This exercise requires a bit of context. Please watch this short video before continuing:

Video Summary

In this course, we've been using CSS Modules for styling. I chose this tool because it had the smallest amount of “learning overhead”. If you know the fundamentals of CSS, you can start using CSS Modules pretty much right away. This is a React course, not a CSS course, and so I went with the quickest option.

In my own projects, though, I use a different tool: 💅 styled-components (opens in new tab).

I wanted to include an exercise about this because there's been a lot of confusion recently around the viability of styled-components. A lot of folks are saying that it doesn't work with Next’s App Router or React Server Components. And so, in this exercise, we're going to dig into these claims.

Even if you have no interest in styled-components, though, I think you'll benefit from attempting this exercise. Ultimately, styled-components is the “setting” for this exercise, but the problems we'll run into are the same sorts of problems you'll hit with any NPM package that needs to run on the client. This isn't really about styled-components, it's about React Server Components.

Alright, so here's the setup:

'use client';
import React from 'react';
import styled from 'styled-components';
const MainWrapper = styled.main`
width: 100%;
max-width: 800px;
padding: 16px 24px;
margin: 0px auto;
border: 1px solid hsl(0deg 0% 50% / 0.3);
border-radius: 2px;
background: white;
`;
function Home() {
return (
<MainWrapper>
<h1>
Exploring the trade-offs with half-gauge vs.
full-gauge wire in laminated woodworking
</h1>
<h2>By Saanvi Agarwal and Vera Chauhan</h2>
<hr />
{/* ✂️ lots of <p> and <figure> tags here */}
</MainWrapper>
);
}
export default Home;

With styled-components, we don't write CSS in CSS files. Instead, we attach chunks of CSS to reusable React components. In this example, we're creating a MainWrapper React component. When we render this component, we'll get a <main> HTML tag with all of the CSS declarations automatically applied.

Here's the problem: styled-components has a “runtime”. This means that it needs to execute some JavaScript in the browser. This is fine in “traditional” React, but it does mean that any React component that uses styled-components needs to be a Client Component. Otherwise, we'll get an error:

createContext only works in Client Components.
Add the "use client" directive at the top of the file to use it.

This is similar to the useState error we saw in the previous lesson. It occurs here because styled-components uses the React Context API internally, and context can only be used in Client Components.

So: we fix this by adding the "use client" directive, and that does solve the problem, but it also means that this entire component will now be bundled and shipped to the client.

I've trimmed it down in this summary, but there's quite a bit of content in this component. All of that JSX transpiles to a bunch of createElement calls:

React.createElement(
'h1',
null,
'Exploring the trade-offs with half-gauge vs. full-gauge wire in laminated woodworking'
),
React.createElement(
'h2',
null,
'By Saanvi Agarwal and Vera Chauhan'
),
React.createElement('hr'),
React.createElement(
'p',
null,
'To begin with, it is crucial to understand the concept of wire gauge. The term "gauge" refers to the diameter of a wire, with the gauge number being inversely proportional to the wire\'s diameter. In simpler terms, a smaller gauge number corresponds to a larger wire diameter, and vice versa. Half-gauge and full-gauge wires derive their names from their respective wire diameters, with half-gauge wire having a diameter that is half that of a full-gauge wire.'
),
// And on and on it goes...

None of these elements are interactive or dynamic. Using the React Server Components mindset, we shouldn't have to include all of this stuff in our JS bundles.

Your mission in this exercise is to find a way to keep all of this content in a Server Component, but to render the styled-components stuff in a Client Component.

Good luck, and have fun!

Acceptance Criteria:

  • The Home component in page.js should be a Server Component (no "use client" directive).
  • The MainWrapper styled component should be a Client Component.
  • As little code as possible should be included in Client Components.

I recognize that it might not be obvious whether or not a component is being used as a Client Component or not; please don't fret! Give it your best shot, and then watch the solution video.

Also: if you'd like to create any new components, you can do that with the new-component package:

$ npx new-component YourComponentName

You can do this in CodeSandbox by clicking the + icon in the right-hand column and selecting “New terminal”. This will allow you to execute this terminal command.

Link to exercise code:

Solution:

Video Summary

The first 1/3rd of this video implements the solution. I've shared that solution code below, so I won't repeat it here.

The rest of the video is dedicated to answering the question: what are the benefits of this refactor? What do we gain by moving the MainWrapper styled component to its own Client Component file?

To measure this, we're going to run the build command:

$ npm run build

We're going to talk more about building and deploying Next applications shortly. For now, we're going to analyze the metrics reported during the process.

Before our refactor, our JS bundle was 90.6kB:

And after our refactor, our JS bundle has been reduced to 88.4kB:

In the solution, we don't ship all of those <p> and <figure> tags to the client, and it reduces the bundle by 2.2kB.

To figure out if this is a significant savings, I used a file transfer time calculator (opens in new tab). On a typical 3G connection speed of 3 mbps, it takes 0.01 seconds (10 milliseconds) to download 2.2kB.

For context, a human blinks in 100-300ms, and it's generally agreed that if something happens in <100ms, it feels instantaneous.

Now, to be fair, it's not only the download time we need to worry about, it's also the time that the browser takes to parse and execute the additional JavaScript code, to hydrate the application. But even if we 10x the amount of time (which is extremely conservative), it's still not likely to make a perceptible difference in terms of performance / user experience.

It's worth remembering that prior to mid-2023 with the release of Next.js, every single React component in every single React application was a Client Component.

We're not losing anything, in terms of performance, by making every component a Client Component. We're just not benefitting from this new optimization, which may or may not be significant.

Now, in some cases, it might very much be worth doing this optimization (eg. for components with lots of content, or an NPM dependency that shouldn't run on the client). My personal takeaway, however, is that the 80/20 rule comes into play here. Refactoring 20% of our components to take advantage of React Server Components could be responsible for 80%+ of the benefits.

We'll learn more about building and deploying Next applications soon.

NOTE: Even if you solved the problem successfully, this is one of those solution videos I recommend watching regardless; the second half of the video digs into the metrics, to see what the actual tangible benefits are.

Revealable Code Snippets with Bright

Earlier, I mentioned Bright (opens in new tab), a syntax-highlighting package designed to be used within React Server Components.

In this exercise, you'll create “revealable” code snippets, as part of a hypothetical Python tutorial:

Acceptance Criteria:

  • Clicking "Reveal Content" should show the relevant code snippet.
  • No compile or runtime errors should be present.
  • Please feel free to refactor / improve the code however you see fit!

Link to exercise code:

Solution:

Drum Machine

In this exercise, we're going to finish wiring up a drum machine. 4 different buttons can be clicked to play a different sample. We can also mute the machine using a separate button:

(🥁 This video requires sound!)

The drum machine itself has already been implemented, using my use-sound NPM package (opens in new tab). Your mission is to wire up the mute button.

Acceptance Criteria:

  • Clicking the icon should toggle a soundEnabled state variable
  • This state variable should control whether or not the 4 drum machine buttons make noise.
  • You're not allowed to move the existing components (eg. you can't move the <Header> element to page.js).

There's quite a bit of “stuff” in this project (I may have gotten a bit carried away with the 90’s design 😅). It might help to spend a few minutes going through the files and learn how everything is connected.

Link to exercise code:

Solution:

If you're feeling a bit rusty when it comes to context, it might be helpful to review the “Provider Components” lesson.